SQLalchemy with column names starting and ending with underscores - python

Set RDBMS_URI env var to a connection string like postgresql://username:password#host/database, then on Python 3.9 with PostgreSQL 15 and SQLalchemy 1.14 run:
from os import environ
from sqlalchemy import Boolean, Column, Identity, Integer
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class Tbl(Base):
__tablename__ = 'Tbl'
__has_error__ = Column(Boolean)
id = Column(Integer, primary_key=True, server_default=Identity())
engine = create_engine(environ["RDBMS_URI"])
Base.metadata.create_all(engine)
Checking the database:
=> \d "Tbl"
Table "public.Tbl"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+----------------------------------
id | integer | | not null | generated by default as identity
Indexes:
"Tbl_pkey" PRIMARY KEY, btree (id)
How do I force the column names with double underscore to work?

I believe that the declarative machinery explicitly excludes attributes whose names start with a double underscore from the mapping process (based on this and this). Consequently your __has_error__ column is not created in the target table.
There are at least two possible workarounds. Firstly, you could give the model attribute a different name, for example:
_has_error = Column('__has_error__', BOOLEAN)
This will create the database column __has_attr__, accessed through Tbl._has_error*.
If you want the model's attribute to be __has_error__, then you can achieve this by using an imperative mapping.
import sqlalchemy as sa
from sqlalchemy import orm
mapper_registry = orm.registry()
tbl = sa.Table(
'tbl',
mapper_registry.metadata,
sa.Column('__has_error__', sa.Boolean),
sa.Column(
'id', sa.Integer, primary_key=True, server_default=sa.Identity()
),
)
class Tbl:
pass
mapper_registry.map_imperatively(Tbl, tbl)
mapper_registry.metadata.create_all(engine)
* I tried using a synonym to map __has_error__ to _has_error but it didn't seem to work. It probably gets exluded in the mapper as well, but I didn't investigate further.

Related

Querying based on related element attributes in SQLAlchemy

For simplicity sake, I will make an example to illustrate my problem.
I have a database that contains a table for baskets (primary keys basket_1, basket_2,..) and a table for fruits (apple_1, apple_2, pear_1, banana_1,...).
Each fruit instance has an attribute that describes its type (apple_1, and apple_2 have an attribute type = 'apple', pear_1 has an attribute type='pear' and so on).
Each basket has a one to many relationship with the fruits (for example basket_1 has an apple_1, an apple_2 and a pear_1).
My question is, given a series of inputs such as [2x elements of type apple and 1 element of type pear], is there a straightforward way to query/find which baskets do indeed contain all those fruits?
I tried something along the lines of:
from sqlalchemy import (
Table, Column, String, ForeignKey, Boolean
)
from sqlalchemy.orm import relationship, declarative_base
from sqlalchemy import (
Table, Column, String, ForeignKey, Boolean
)
from sqlalchemy.orm import relationship, declarative_base
from sqlalchemy.orm import sessionmaker
# Create session
database_path = "C:/Data/my_database.db"
engine = create_engine(database_path)
session = sessionmaker()
session.configure(bind=engine)
# Model
class Basket(Base):
__tablename__ = "baskets"
id = Column(String, primary_key=True)
fruits = relationship("Fruit",backref='baskets')
class Fruit(Base):
__tablename__ = "fruits"
id = Column(String, primary_key=True)
type = Column(String)
parent_basket = Column(String, ForeignKey('basket.id'))
# List of fruits
fruit_list = ['apple', 'apple', 'pear']
# Query baskets that contain all those elements (I currently have no idea on how to set up the condition or should I use a 'join' in this query)
CONDITION_TO_FILTER_ON = "basket should contain as many fruits of each type as specified in the fruit list"
baskets = session.query(Basket).filter(CONDITION_TO_FILTER_ON)
Sorry if the phrasing/explanation is not clear enough. I've been playing around with filters but it still isn't clear enough to me how to approach this.

SQLAlchemy query where a column is a substring of another string

This question is similar to SQLAlchemy query where a column contains a substring, but the other way around: I'm trying to query a column containing a string which is a sub-string of another given string. How can I achieve this?
Here is a code example of a database set up using the ORM:
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql import exists
engine = create_engine('sqlite:///:memory:')
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
url = Column(String)
fullname = Column(String)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
session.add_all([
User(url='john', fullname='John Doe'),
User(url='mary', fullname='Mary Contrary')
])
session.commit()
The following works:
e = session.query(exists().where(User.url == 'john')).scalar()
upon which e has the value True. However, I would like to do something like
e = session.query(exists().where(User.url in 'johndoe')).scalar()
where in is in the sense of the __contains__ method of Python's string type. Is this possible?
It's just like (heh) the linked question, except you turn it around:
SELECT ... WHERE 'johndoe' LIKE '%' || url || '%';
You'll need to take care to escape special characters if you've got those in your table:
SELECT ... WHERE 'johndoe' LIKE '%' || replace(replace(replace(url, '\', '\\'), '%', '\%'), '_', '\_') ESCAPE '\';
In SQLAlchemy:
escaped_url = func.replace(func.replace(func.replace(User.url, "\\", "\\\\"),
"%", "\\%"),
"_", "\\_")
session.query(... .where(literal("johndoe").like("%" + escaped_url + "%", escape="\\")))
Note the escaped backslashes in Python.
You can use like
e = session.query(exists().where(User.url.like("%{}%".format('put your string here')))).scalar()

How to check inserting row on duplicity based on multiple attributes?

I'm working in SQLAlchemy. Is it possible to set equality of two rows so if the row is being inserted and there exists a row with the same 2 columns already , lets say 'creation_date' and 'destination_from', then the second row wont be inserted?
I don't want to create a PRIMARY KEY on those columns.
I suppose that checking manually whether there is already a row with those columns is unefficient.
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy import Column
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
import datetime
engine = create_engine('sqlite:///db.db', echo=False)
Base = declarative_base()
s = sqlalchemy.orm.Session(engine)
class Flight(Base):
__tablename__ = 'flights'
id = Column(sqlalchemy.Integer, primary_key=True)
destination_from = Column(sqlalchemy.String)
destination_to = Column(sqlalchemy.String)
creation_date = Column(sqlalchemy.Date)
start_date = Column(sqlalchemy.Date)
return_date = Column(sqlalchemy.Date)
price = Column(sqlalchemy.Float)
Base.metadata.create_all(engine)
def insert_into_flights(**kwargs):
s.add(Flight(**kwargs))
s.commit()
You don't want to create a PRIMARY KEY, but can't you create a UNIQUE CONSTRAINT?
UniqueConstraint('creation_date', 'destination_from')

How to specify the primary id when inserting rows with sqlalchemy when id dos not have autoincrement?

I do have database table that has an id primary key that is not an auto-increment (sequence). So it's up to the user to create an unique id or the insert will fail.
This table is not under my control, so I cannot change the database structure.
from sqlalchemy import create_engine, Table, MetaData
import psycopg2
db = create_engine('postgresql://...', echo=False).connect()
meta = MetaData()
meta.reflect(bind=db)
t = Table("mytable", meta, autoload=True, autoload_with=db)
values = { "title":"title", "id": ... }# ???
t.insert(bind=db, values=values).execute()
Given this is "single-user" / "single-client" system, you should be able to use the Column defaults: Python-Executed Functions. The example on the documentation linked to is enough to get you started. I would, however, use python function but with proper initialization from the datatabase adn still stored in a global variable:
def new_id_factory():
if not('_MYTABLE_ID_' in globals()):
q = db.execute("select max(mytable.id) as max_id from mytable").fetchone()
_MYTABLE_ID_ = (q and q.max_id) or 0
_MYTABLE_ID_ += 1
return _MYTABLE_ID_
t = Table("mytable", Base.metadata,
Column('id', Integer, primary_key=True, default=new_id_factory), #
autoload=True, autoload_with=db,
)

sqlalchemy - reflecting tables and columns with spaces

How can I use sqlalchemy on a database where the column names (and table names) have spaces in them?
db.auth_stuff.filter("db.auth_stuff.first name"=='Joe') obviously can't work. Rather than manually define everything when doing the reflections I want to put something like lambda x: x.replace(' ','_') between existing table names being read from the db, and being used in my models. (It might also be useful to create a general function to rename all table names that won't work well with python - reserved words etc.)
Is there an easy/clean way of doing this?
I think I need to define my own mapper class?
https://groups.google.com/forum/#!msg/sqlalchemy/pE1ZfBlq56w/ErPcn1YYSJgJ
Or use some sort of __mapper_args__ parameter -
http://docs.sqlalchemy.org/en/rel_0_8/orm/mapper_config.html#naming-all-columns-with-a-prefix
ideally:
class NewBase(Base):
__mapper_args__ = {
'column_rename_function' : lambda x: x.replace(' ','_')
}
class User(NewBase):
__table__ = "user table"
}
you can do this using a reflection event to give the columns a .key, however the full recipe has a bug when primary key columns are involved, which was fixed in the still-unreleased 0.8.3 version (as well as master). If you check out 0.8.3 at https://bitbucket.org/zzzeek/sqlalchemy/get/rel_0_8.zip this recipe will work even with primary key cols:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base, DeferredReflection
Base = declarative_base(cls=DeferredReflection)
e = create_engine("sqlite://", echo=True)
e.execute("""
create table "user table" (
"id col" integer primary key,
"data col" varchar(30)
)
""")
from sqlalchemy import event
#event.listens_for(Table, "column_reflect")
def reflect_col(inspector, table, column_info):
column_info['key'] = column_info['name'].replace(' ', '_')
class User(Base):
__tablename__ = "user table"
Base.prepare(e)
s = Session(e)
print s.query(User).filter(User.data_col == "some data")
DeferredReflection is an optional helper to use with declarative + reflection.

Categories