I am creating an endpoint that will effect a reversal using fastApi with python.I have an endpoint that takes in an id via post request & calls a reverse function on the transaction model. But I get the error AttributeError: 'Session' object has no attribute 'session' on 'db.session.add(transaction)' . I am not sure where this is coming from.
Transaction model
class Transaction(Base):
__tablename__ = "transactions"
id = Column(Integer, primary_key=True, index=True)
uuid = Column(UUID(as_uuid=True), nullable=False, default=uuid.uuid4)
amount = Column(Numeric(18, 2))
type = Column(
Enum(TransactionTypes, name="transaction_trans_types"), nullable=False
)
wallet_type = Column(
Enum(WalletTypes, name="transaction_wallet_types"), nullable=False
)
created_when = Column(DateTime(timezone=True), server_default=func.now())
payment_ref = Column(String, nullable=False)
running_balance = Column(Numeric(18, 2))
currency = Column(String, default="KES")
notes = Column(String)
paid_by = Column(UUID(as_uuid=True), ForeignKey("patients.uuid"), nullable=True,)
patient_uuid = Column(UUID(as_uuid=True), ForeignKey("patients.uuid"))
wallet_uuid = Column(UUID(as_uuid=True), ForeignKey("wallets.uuid"))
paid_by_names = Column(String)
def reverse(self,db: Session,notes:str =None,):
transaction =Transaction(amount =self.amount,type= self.type,wallet_type = self.wallet_type,payment_ref = self.payment_ref,
notes= notes )
db.session.add(transaction)
db.session.commit()
db.session.refresh(transaction)
reversal = Reversal(transaction_uuid=transaction.uuid,reversed_transaction_uuid= self.reversed_transaction_uuid)
db.session.add(reversal)
db.session.commit()
db.session.refresh(reversal)
return transaction
Reversal Model
class Reversal(Base):
__tablename__ = "reversals"
id = Column(Integer, primary_key=True, index=True)
transaction_uuid = Column(UUID(as_uuid=True), ForeignKey("transactions.uuid"))
reversed_transaction_uuid = Column(UUID(as_uuid=True),ForeignKey("transactions.uuid"))
__table_args__ = (
UniqueConstraint("reversed_transaction_uuid"),
)
Endpoint
#router.post("/{transaction_id}/reverse",)
async def reverse_transaction(
*,
db: Session = Depends(deps.get_db),
transaction_id: int,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""
Reverse transaction
"""
if not current_user.is_superuser:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not allowed to reverse transactions",
)
try:
transaction = (
db.query(Transaction).filter(Transaction.id == transaction_id).scalar()
)
transaction.reverse(db,transaction)
except NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Not found",
)
return transaction
This is my deps.db
def get_db() -> Generator:
try:
db = SessionLocal()
yield db
finally:
db.close()
The most important part would be to see how deps.get_db() looks like.
But according to your type hints I guess, this already gives you a Session. So instead of db.session in your reverse method, simply use db.
Related
I have an issue with SQLAlchemy and I cannot figure out the cause of this error:
so my class definition is:
class PricingFrequency(enum.Enum):
month = 'month'
year = 'year'
class PlanPricing(Base):
__tablename__ = "PlansPricing"
pricing_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
.....
subscription_plan = relationship("SubscriptionPlan", back_populates="plans_pricing")
plan_id = Column(UUID(as_uuid=True), ForeignKey("SubscriptionPlans.plan_id"))
created_on = Column(DateTime, server_default=func.now())
updated_on = Column(DateTime, server_default=func.now(), server_onupdate=func.now())
class SubscriptionPlanOption(Base):
__tablename__ = "SubscriptionPlanOptions"
option_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
.....
subscription_plan = relationship("SubscriptionPlan", back_populates="options_plan")
plan_id = Column(UUID(as_uuid=True), ForeignKey("SubscriptionPlans.plan_id"))
created_on = Column(DateTime, server_default=func.now())
updated_on = Column(DateTime, server_default=func.now(), server_onupdate=func.now())
class SubscriptionPlan(Base):
__tablename__ = "SubscriptionPlans"
plan_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
plan_name = Column(String)
plan_description = Column(String)
is_popular = Column(Boolean, default=False)
plans_pricing: List[Any] = relationship("PlanPricing", back_populates="subscription_plan") # , lazy='joined')
options_plan: List[Any] = relationship("SubscriptionPlanOption",
back_populates="subscription_plan") # lazy='joined')
created_on = Column(DateTime, server_default=func.now())
updated_on = Column(DateTime, server_default=func.now(), server_onupdate=func.now())
When I make this query :
query = (
select(SubscriptionPlan)
.options(joinedload(SubscriptionPlan.options_plan, innerjoin=True),
joinedload(SubscriptionPlan.plans_pricing.and_(PlanPricing.pricing_id == pricing_id),
innerjoin=True))
)
items = await session.execute(query)
items = items.scalars().all()
I got this error message:
**The unique() method must be invoked on this Result, as it contains results that include joined eager loads against collections**
Note : session is AsyncSession
Can anyone explain the source of this issue?
Thanks
I misunderstood sqlalchemy's loading technique. Nevertheless, my request was not well formatted, so I changed it to be that way, and it is currently functioning.
plan_pricing_aliased1 = aliased(PlanPricing)
plan_pricing_aliased2 = aliased(PlanPricing)
query = (
select(
plan_pricing_aliased1.pricing_id,
plan_pricing_aliased2.pricing_id
).join_from(
plan_pricing_aliased1,
plan_pricing_aliased2,
and_(
plan_pricing_aliased2.currency == plan_pricing_aliased1.currency,
plan_pricing_aliased2.plan_id == plan_pricing_aliased1.plan_id,
plan_pricing_aliased2.pricing_frequency == plan_pricing_aliased1.pricing_frequency
)
).where(
and_(
plan_pricing_aliased1.pricing_id == pricing_id,
plan_pricing_aliased1.price > 0,
plan_pricing_aliased2.price == 0
)
)
)
items = await session.execute(query)
items = items.first()
return items
When i try to add user with BIG INT, psycopg2 throws Numberic Value out of range error.
but still BIGINT is my data type in Column of my table and in local postgresql database.
Edit1: I have added the source code for chat_members model.
My table Schema code
class Users(BASE):
__tablename__ = "users"
user_id = Column(BigInteger, primary_key=True)
username = Column(UnicodeText)
def __init__(self, user_id, username=None):
self.user_id = user_id
self.username = username
def __repr__(self):
return "<User {} ({})>".format(self.username, self.user_id)
class Chats(BASE):
__tablename__ = "chats"
chat_id = Column(String(14), primary_key=True)
chat_name = Column(UnicodeText, nullable=False)
def __init__(self, chat_id, chat_name):
self.chat_id = str(chat_id)
self.chat_name = chat_name
def __repr__(self):
return "<Chat {} ({})>".format(self.chat_name, self.chat_id)
class ChatMembers(BASE):
__tablename__ = "chat_members"
priv_chat_id = Column(Integer, primary_key=True)
# NOTE: Use dual primary key instead of private primary key?
chat = Column(
String(14),
ForeignKey("chats.chat_id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=False,
)
user = Column(
BigInteger,
ForeignKey("users.user_id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=False,
)
__table_args__ = (UniqueConstraint("chat", "user", name="_chat_members_uc"),)
def __init__(self, chat, user):
self.chat = chat
self.user = user
def __repr__(self):
return "<Chat user {} ({}) in chat {} ({})>".format(
self.user.username,
self.user.user_id,
self.chat.chat_name,
self.chat.chat_id,
)
Users.__table__.create(checkfirst=True)
Chats.__table__.create(checkfirst=True)
ChatMembers.__table__.create(checkfirst=True)
code which throws error:
def update_user(user_id, username, chat_id=None, chat_name=None):
with INSERTION_LOCK:
user = SESSION.query(Users).get(user_id)
if not user:
user = Users(user_id, username)
SESSION.add(user)
SESSION.flush()
else:
user.username = username
if not chat_id or not chat_name:
SESSION.commit()
return
chat = SESSION.query(Chats).get(str(chat_id))
if not chat:
chat = Chats(str(chat_id), chat_name)
SESSION.add(chat)
SESSION.flush()
else:
chat.chat_name = chat_name
member = (
SESSION.query(ChatMembers)
.filter(ChatMembers.chat == chat.chat_id, ChatMembers.user == user.user_id)
.first()
)
if not member:
chat_member = ChatMembers(chat.chat_id, user.user_id)
SESSION.add(chat_member)
SESSION.commit()
my init.py file:
def start() -> scoped_session:
engine = create_engine(DB_URI, client_encoding="utf8")
BASE.metadata.bind = engine
BASE.metadata.create_all(engine)
return scoped_session(sessionmaker(bind=engine, autoflush=False))
BASE = declarative_base()
SESSION = start()
Tracelog:
sqlalchemy.exc.DataError: (psycopg2.errors.NumericValueOutOfRange) integer out of range
[SQL: INSERT INTO chat_members (chat, "user") VALUES (%(chat)s, %(user)s) RETURNING chat_members.priv_chat_id]
[parameters: {'chat': '-1001779301880', 'user': 5030277858}]
(Background on this error at: https://sqlalche.me/e/14/9h9h)
I'm trying to implement a basic resource access using SQL Alchemy 1.4 and a PostgreSQL database.
Python code
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
Base: DeclarativeMeta = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
email = Column(String(length=255), index=True, nullable=False)
class Resource(Base):
__tablename__ = "resource"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String(length=255), index=True, nullable=False)
class UserResourceRole(Base):
__tablename__ = "user_resource_role"
user_id = Column(
UUID(as_uuid=True),
ForeignKey("user.id", ondelete="CASCADE"),
primary_key=True
)
resource_id = Column(
UUID(as_uuid=True),
ForeignKey("resource.id", ondelete="CASCADE"),
primary_key=True,
)
can_edit = Column(Boolean, default=False, nullable=False)
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.asyncio.engine import AsyncEngine
from sqlalchemy.orm import sessionmaker
from the_other_file import User, Resource, UserResourceRole
async def select_all(user: User, db_session: AsyncSession):
results = await db_session.execute(
select(Resource, UserResourceRole)
.join(
UserResourceRole,
Resource.id == UserResourceRole.resource_id
)
.where(UserResourceRole.user_id == user.id)
)
return results.scalars().all()
engine: AsyncEngine = create_async_engine(POSTGRES_URL, future=True)
async_session = sessionmaker(
bind=engine, class_=AsyncSession, expire_on_commit=False, future=True
)
# ignore the fact that it's not executed in asyncio loop
a_db_session = await async_session()
resources = await select_all(user=a_real_user_is_here, db_session=a_db_session)
print(resources)
I can't retrieve anything from UserResourceRole in my results. It only contains data from Resource. If i swap the objects in the select call, then I can only retrieve data from UserResourceRole.
What I'm expecting
I'm expecting to have the same result of that SQL query :
SELECT *
FROM resource
INNER JOIN user_resource_role
ON resource.id = user_resource_role.resource_id
WHERE user_resource_role.user_id = :a_user_id
The query generated by SQL Alchemy is exactly the same (except the verbosity) :
SELECT resource.id, resource.name, user_resource_role.user_id, user_resource_role.resource_id, user_resource_role.can_edit
FROM resource
JOIN user_resource_role
ON resource.id = user_resource_role.resource_id
WHERE user_resource_role.user_id = :user_id_1
If you try
for entry in results:
print(entry)
it will show you a list of tuple of (Resource, UserResourceRole). Apparently the call to .scalars().all() only leave the first value.
My current solution is to turn results into a list and manually manipulate it.
Remove scalars(). So, it should be
return results.all()
This will return a list of tuples
[(resource_1, userresourcerole_1),(resource_2, userresourcerole_2),...]
I have these models
class Integration(Base, ModelBase, ModelSerializer):
__tablename__ = 'integration'
id = Column(Integer, primary_key=True)
domain = relationship('Domain', backref='integration')
created_at = Column(DateTime, default=datetime.now())
updated_at = Column(DateTime, default=datetime.now())
name = Column(String(512), index=True)
meta = Column(JSON)
tag = Column(String(512))
identifier_id = Column(String(512), unique=True, index=True)
def __repr__(self):
return '<name {}>'.format(self.name)
class Domain(Base, ModelBase, ModelSerializer):
__tablename__ = 'domain'
id = Column(Integer, primary_key=True)
integration_id = Column(Integer, ForeignKey('integration.id'), index=True)
url = relationship('Url', backref='domain')
created_at = Column(DateTime, default=datetime.now())
updated_at = Column(DateTime, default=datetime.now())
name = Column(String(512), index=True)
domain = Column(String(512), nullable=True)
def __repr__(self):
return '<name {}>'.format(self.name)
class Url(Base, ModelBase, ModelSerializer):
__tablename__ = 'url'
id = Column(Integer, primary_key=True)
domain_id = Column(Integer, ForeignKey('domain.id'), index=True)
created_at = Column(DateTime, default=datetime.now())
updated_at = Column(DateTime, default=datetime.now())
name = Column(String(512), index=True)
url = Column(String(512), nullable=True)
meta = Column(JSON)
auth = Column(JSON)
def __repr__(self):
return '<name {}>'.format(self.name)
class ModelBase(object):
def __repr__(self):
return '<id {}>'.format(self.id)
class ModelSerializer(object):
def as_dict(self):
return {attribute.name: getattr(self, attribute.name) for attribute in self.__table__.columns}
Now I need to filter out the data based on the filters.
If suppose integration_filters are given then only filter the Integration data.
If Integration and Domain are given then filter only domain and integration by joining them
Same with Url and Domain
Solutions I tried -
1). Doesnt work as it doesnt join then tables. Later figured out that add_entity doesnt add the models.
if integration_filter:
query = query.add_entity(Integration)
query = query.filter_by(**kwargs['integration_filter'])
if domain_filter:
query = query.add_entity(Domain)
query = query.filter_by(**kwargs['domain_filter'])
if url_filter:
query = query.add_entity(Url)
query = query.filter_by(**kwargs['url_filter'])
2). It checks for the attribute 'domain' in Integration table
if integration_filter:
query = session.query(Integration).filter_by(**integration_filter)
if domain_filter:
if query:
query = query.join(Domain)
else:
query = session.query(Domain)
query = query.filter_by(**domain_filter)
if url_filter:
if query:
query = query.join(Url)
else:
query = session.query(Url)
query = query.filter_by(**url_filter)`
3). This doesnt work either
models = []
joins = []
if integration_filter:
models.append(Integration)
if domain_filter:
if models:
joins.append((Integration, Domain, Integration.id == Domain.integration_id))
models.append(Domain)
if url_filter:
if models:
joins.append((Domain, Url, Domain.id == Url.domain_id))
models.append(Url)
query = session.query(*models)
for join in joins:
query = query.join(*join)
This one worked. Was pretty simple though, didn't think of it in the beginning
try:
session = Backend().get_session()
models = []
join_filter = []
if integration_filter:
models.append(Integration)
join_filter.append({'filter': integration_filter})
if domain_filter:
models.append(Domain)
join_filter.append({'join': (Domain, Domain.integration_id == Integration.id), 'filter': domain_filter})
if url_filter:
models.append(Url)
join_filter.append({'join': (Url, Url.domain_id == Domain.id), 'filter': url_filter})
query = session.query(*models)
for jf in join_filter:
if 'join' in jf:
query = query.join(jf['join'])
if 'filter' in jf:
query = query.filter_by(**jf['filter'])
query = query.__getattribute__(query_type)
self.records = query()
except InvalidRequestError as ir:
raise Exception("Invalid query in URL Builder Error")
except NoResultFound as nrf:
raise Exception(
"No URL was found for filters integration - {} domain - {} url - {}".format(integration_filter,
domain_filter, url_filter))
except MultipleResultsFound as mrf:
raise Exception(
"Multiple Results was found for filters integration - {} domain - {} url - {}".format(
integration_filter, domain_filter, url_filter))
except Exception as e:
raise Exception("Error while fetching records in URL Builder {}")
finally:
session.close()
I have two tables:
class Project(DataBase):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
name = Column(String, nullable=False, unique=True)
domain = Column(String, nullable=False)
phrases = relationship("Phrase", backref='proj')
def __init__(self, name, domain):
self.name = name
self.domain = domain
class Phrase(DataBase):
__tablename__ = 'phrases'
query_id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
query_text = Column(String, nullable=False)
project = Column(Integer, ForeignKey('projects.id'), nullable=False)
enable = Column(Boolean, nullable=False, default=True)
def __init__(self, query_text, project, city):
self.query_text = query_text
self.project = project
And I have a function:
def get_first_query():
session = Session(bind=engine)
q = session.query(Phrase).filter(Phrase.enable == True).first()
session.close()
return q
I want to get an object from table 2 and than get its parrent from first table:
session = Session(bind=engine)
q = get_first_query()
print(q.proj)
It doesn't work and print this error:
sqlalchemy.orm.exc.DetachedInstanceError: Parent instance is not bound to a Session; lazy load operation of
attribute 'proj' cannot proceed
I can do this:
session = Session(bind=engine)
q = get_first_query()
q_project = session.query(Project).filter(Project.id == q.project)
But it's a bad way.
You can assess related object via proj attribute.
session.query(Phrase).filter(
Phrase.enable == True
).first().proj
This way you'll hit database one additional time to get it so you'll need to open session again. To avoid additional queries you can use joined load:
session.query(Phrase).filter(
Phrase.enable == True
).options(
joinedload('proj')
).first().proj