Following this example I am trying to insert or update values based on a constraint. I was wondering how to set the table defaults if a value is null as you could do with pure sql string.
So far I have tried this:
....
_listing_insert_stmt = psql.insert(db.List2).values(data)
dict_keys = dict(
ChainMap(
*list(
map(
lambda x: {x[0]: getattr(_listing_insert_stmt.excluded, x[0])},
_listing_insert_stmt.excluded._collection,
),
)
)
)
listing_insert_stmt = _listing_insert_stmt.on_conflict_do_update(
constraint="ix_listings_foreign_id", set_={**dict_keys,"datetime_created":_listing_insert_stmt.excluded.datetime_created.default, "last_edited":datetime.utcnow().astimezone(pytz.UTC)}
).returning(db.List2)
listing_execution_stmt = (
select(db.List2)
.from_statement(listing_insert_stmt)
.execution_options(populate_existing=True)
)
for listing in s.execute(listing_execution_stmt).scalars():
print("inserted or updated: %s" % listing)
the table columns returned in excludes appears to not have a default set, even though it is set on my original db model
class BaseMixin(object):
id = sa.Column(psql.UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
datetime_created = sa.Column(psql.TIMESTAMP(True), server_default=sa.func.now())
last_edited = sa.Column(
psql.TIMESTAMP(True), server_default=sa.func.now(), onupdate=sa.func.now()
)
I'm wondering if the issue is happening because I am declaring the above fields as a mixin that my listings table inherits from?
class List2(Base, BaseMixin):
__tablename__ = "listings"
__table_args__ = (sa.UniqueConstraint("foreign_id"),)
I would expect it to work as this does:
INSERT INTO t (foo,bar) VALUES (1,2)
ON CONFLICT (foo) DO UPDATE
SET bar = DEFAULT;
Related
I have, on a pgsql backend, a table and an (allegedly) random access version of it:
class Topic(db.Model):
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
question_id = db.Column(UUID(as_uuid=True), db.ForeignKey("question.id"))
topic = db.Column(Enum(QuestionTopicsChoices))
# Create an alias for Topic that uses BERNOULLI sampling method
Topic_rnd = db.aliased(Topic, tablesample(Topic, func.bernoulli(2.5), 10))
I'd expect, then, that these two queries return with different results:
topics_linear = Topic.query.filter_by(topic='emotion').all()
topics_random = Topic_rnd.query.filter_by(topic='emotion').all()
but they return the records in the same order. What am I doing wrong?
Very simple trying to run a query in Python 3 sqlalchemy to delete some records given string names of table and field to query against.
How do you get the table object from a string?
Given 1. how do you run a query via ORM with just a string of the field name?
I would assume all ORM's have an internal array or method like get with the name.
json_config = [
{"table": "tableA",
"field": "modified_on"
"expires": 30},
{"table": "tableB",
"field": "event_on"
"expires": 30}
]
for table_conf_item in self.json_config:
table_name = table_conf_item["table"]
field_name = table_conf_item["field"]
expire_after = table_conf_item["expires"]
table_obj = self.orm_session.TABLES[table_name]
field_obj = self.orm_session.TABLES[table_name].FIELDS[field_name]
result = self.orm_session.delete(table_obj).where(field_obj < expire_after)
self.orm_session.commit()
print(f"{table_name}: removed {result.row_count} objects")
Given the table's name, you can use reflection to get a Table object. Using SQLAlchemy's core layer, this is reasonably straightforward:
import sqlalchemy as sa
engine = sa.create_engine(...)
tbl = sa.Table(name_of_table, metadata, autoload_with=engine)
If you want to work with multiple tables, it may be more efficient to store them a Metadata instance for later access:
metadata = sa.MetaData()
metadata.reflect(engine, only=list_of_table_names)
tbl = metadata.tables[name_of_table]
Once you have a Table object you can reference columns by name like this: tbl.c[name_of_field].
Full example:
import sqlalchemy as sa
# Setup
engine = sa.create_engine('sqlite://', echo=True, future=True)
tbl = sa.Table(
't',
sa.MetaData(),
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('foo', sa.Integer),
)
tbl.create(engine)
with engine.begin() as conn:
vals = [42, 43, 42, 43, 56, 87, 89]
conn.execute(tbl.insert(), [{'foo': v} for v in vals])
del tbl
# Reflect the table.
metadata = sa.MetaData()
metadata.reflect(engine, only=['t'])
tbl = metadata.tables['t']
# Define some statements.
q1 = sa.select(tbl).where(tbl.c['foo'] == 42)
q2 = sa.select(tbl.c['id'], tbl.c['foo']).where(tbl.c['foo'] == 43)
q3 = sa.delete(tbl).where(tbl.c['foo'] != 42)
# Execute the statements.
with engine.connect() as conn:
rows = conn.execute(q1)
for row in rows:
print(row)
print()
rows = conn.execute(q2)
for row in rows:
print(row)
print()
with engine.begin() as conn:
conn.execute(q3)
with engine.connect() as conn:
rows = conn.execute(q1)
for row in rows:
print(row)
print()
Doing the same through the ORM layer is more complicated, as table and column names must be mapped to ORM entity classes (models) and their attributes. This replicates the previous example for a simple mapping (it assumes the same initial data as above).
import sqlalchemy as sa
from sqlalchemy import orm
Base = orm.declarative_base()
class Thing(Base):
__tablename__ = 't'
id = sa.Column(sa.Integer, primary_key=True)
thing_foo = sa.Column('foo', sa.Integer)
engine = sa.create_engine(...)
Base.metadata.create_all(engine)
Session = orm.sessionmaker(engine, future=True)
tablename = 't'
columnname = 'foo'
with Session.begin() as s:
# Get the mappers for the Base class.
mappers = Base.registry.mappers
# Get the mapper for our table.
mapper = next(m for m in mappers if m.entity.__tablename__ == tablename)
# Get the entity class (Thing).
entity = mapper.entity
# Get the column from the Table.
table_column = mapper.selectable.c[columnname]
# Get the mapper property that corresponds to the column
# (the entity attribute may have a different name to the
# column in the database).
mapper_property = mapper.get_property_by_column(table_column)
# Get the queryable entity attribute (Thing.thing_foo).
attr = mapper.all_orm_descriptors[mapper_property.key]
q = sa.select(entity).where(attr != 42)
entities = s.scalars(q)
for entity in entities:
s.delete(entity)
with Session() as s:
for thing in s.scalars(sa.select(Thing)):
print(thing.id, thing.thing_foo)
I am not able to iterate through my query as I would like using Peewee
Those are the related Objects in Models.py
class Conversation(peewee.Model):
id = peewee.AutoField(unique=True, index=True)
creation_date = peewee.DateTimeField(default=datetime.now)
contact_id = ForeignKeyField(Contact, backref='conversation')
launch_id = ForeignKeyField(Launch, backref='conversation')
request_data = peewee.TextField(null=True)
status = peewee.TextField(null=True)
class Contact(peewee.Model):
id = peewee.AutoField(unique=True, index=True)
uuid = peewee.CharField(default=shortuuid.uuid, index=True)
whatsapp_phone = peewee.CharField(index=True, default='')
status = peewee.CharField(default='init')
conversationId = peewee.CharField(null=True)
Here's how I am trying to iterate:
for conversation in Conversation.select().where(Conversation.launch_id == str(launch_id)):
print(conversation.contact.id)
And this is the error that I a getting:
print(conversation.contact.id)
AttributeError: 'Conversation' object has no attribute 'contact'
I've tried to change the way I do my query:
query = Conversation.select(Contact).join(Contact).where(Conversation.launch_id == str(launch_id))
But I get the exact same error if I iterate in the same way.
The issue is you are, for some reason, trying to access .contact when you've named your foreign-key .contact_id. The peewee docs are clear about foreign key naming, but you want this:
class Conversation(peewee.Model):
id = peewee.AutoField(unique=True, index=True)
creation_date = peewee.DateTimeField(default=datetime.now)
# Data will be stored in a column named "contact_id":
contact = ForeignKeyField(Contact, backref='conversations')
# Data will be stored in a column named "launch_id":
launch = ForeignKeyField(Launch, backref='conversations')
request_data = peewee.TextField(null=True)
status = peewee.TextField(null=True)
This allows:
query = (Conversation
.select()
.where(Conversation.launch == str(launch_id)))
for conversation in query:
# Access the underlying foreign-key value.
print(conversation.contact_id)
Or, if you intend to access other fields on the Contact:
query = (Conversation
.select(Conversation, Contact)
.join(Contact)
.where(Conversation.launch == str(launch_id)))
for conversation in query:
# We now have a "full" Contact instance we can access efficiently:
print(conversation.contact.id)
Please read the docs:
http://docs.peewee-orm.com/en/latest/peewee/quickstart.html#lists-of-records
http://docs.peewee-orm.com/en/latest/peewee/relationships.html
http://docs.peewee-orm.com/en/latest/peewee/models.html#foreignkeyfield
class Category(models.Model):
name = models.CharField(max_length=100)
date = models.DateTimeField(auto_now=True)
class Hero(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
I want Categoty model name, data, id
In cookbook , I wrote the code as above.
hero_qs = Hero.objects.filter(
category=OuterRef("pk")
).order_by("-benevolence_factor")
Category.objects.all().annotate(
most_benevolent_hero=Subquery(
hero_qs.values('name')[:1]
)
)
It seems that only one value can be entered in hero_qs.values('name')
Is it possible to get name, data, id with one annotate?
You can try Concatenating the fields if you really want to use a single annotation
from django.db.models import Subquery, OuterRef, CharField, Value as V
from django.db.models.functions import Concat
hero_qs = Hero.objects.filter(
category=OuterRef("pk")
).order_by("-benevolence_factor").annotate(
details=Concat('name', V(','), 'id', output_field=CharField())
)
Category.objects.all().annotate(
most_benevolent_hero=Subquery(
hero_qs.values('details')[:1]
)
)
Then you can use string interpolation to separate that data out which is a relatively inexpensive operation
name, id = category.most_benevolent_hero.split(',')
Trying to join the tables below using this command:
Subscription.query.filter( return Subscription.query.filter(Subscription.watch_id == id).join(User).filter_by(watch_id=id)
I get this error:
sqlalchemy.exc.InvalidRequestError: Could not find a FROM clause to join from. Tried joining to <class 'app.user.model.User'>, but got: Can't find any foreign key relationships between 'wm_subscription' and 'user'.
Essentially my end goal is to get a query that gets a List of Users that share a watch_id. Not sure if the models or the query is correct. Anybody know what's wrong?
Database = declarative_base(cls=DbBase)
class Subscription(Database):
__tablename__ = 'wm_subscription'
subscription_id = UniqueIdPk()
watch_id = UniqueIdRefNotNull(index=True)
user_id = UniqueIdRefNotNull(ForeignKey('User.user_id'), index=True)
subscription_watch = relationship('Watch',
primaryjoin='Subscription.watch_id == Watch.watch_id',
foreign_keys='Watch.watch_id',
uselist=True)
subscription_user = relationship('User',
primaryjoin='Subscription.watch_id == User.user_id',
foreign_keys='User.user_id',
uselist=True,
backref='user')
class User(Database, UserMixin):
__tablename__ = 'user'
user_id = UniqueIdPk()
# Google sub ID - unique to user https://developers.google.com/identity/protocols/OpenIDConnect
google_id = Column(String(length=50))
# override email mixin for unique index
email = Email(unique=True)
first_name = Name()
last_name = Name()
def get_id(self):
return self.user_id
This is the correct query:
Subscription.query.filter(Subscription.watch_id == id).join(User)