Thank you, as always, for both the specific answer and the general advice. Much appreciated!
On Sunday, June 23, 2019 at 5:19:49 PM UTC-5, Mike Bayer wrote: > > > > On Sun, Jun 23, 2019, at 5:45 PM, Andrew Martin wrote: > > I have a generic CRUDService for my web app. It's a pattern that was > loosely suggested to me by Mike a while back. I've probably not implemented > it the way he intended, but it works pretty well in a pretty small amount > of code. > > > The main work, however, that needs to be done is in the read_all method. > It needs to be able to handle different filters depending on which view is > calling it. Which it can as it is now. But I want to fidget with the > defaults a little. Basically, I almost never want to show objects that have > been soft deleted. Only admin users/views should be able to fidget with > things that we've "deleted". The way the method works now is that if I > don't pass any filters in at all, it sets a filter to > cls.is_deleted==False. But I'd like to be able to inspect incoming filters. > Say the view looks at the resource context and determines that a view > should only be allowed to see objects based on some ACL rule. I might > create a filter for that view controller that looks like > filters = (Project.usergroup==User.usergroup) > > (defining the applicable usergroup for a given context is a separate > service, but the relationship should be obvious, I think?) > > and then have the view query with CRUDService.read_all(Project, > request.dbsession, filters). The way I've got it now, that would just > replace the default filter, and I would have to add > Project.is_deleted==False to the tuple of filters every time I call this. I > would like to be able to inspect the filters that are passed into the > read_all (which, honestly, I should rename and call it read_many) method > and say, if there isn't any reference to the is_deleted column in the > filters, then set that as false, otherwise, accept the value in the > function parameter. But I find the filter's tuple values quite opaque. Once > they are instantiated, they are class-specific, and I'm trying to get at a > general way of understanding them. I'll note that this column is guaranteed > to be present in the framework. I've modified the declarative base such > that every object has the is_deleted. I appreciate any help you might be > able to give. It's very likely I'm overthinking this like a numbskull and > the answer is painfully obvious. Here's the service class: > > > you can inspect a ColumnElement structure to look for a certain column > like this: > > from sqlalchemy.sql import visitors > for elem in visitors.iterate(Project.usergroup == User.usergroup, {}): > if isinstance(elem, Column) and elem.name == "is_deleted": > return True > > internally, the ORM usually uses visitors.traverse(expression, {}, > {"column": visit_column}) but the above is easier to illustrate quickly. > > If it were me, I'd likely keep this as an explicit option on read_all(), > e.g. read_all(... include_deleted=True), much simpler and won't be called > "magic" by anyone, but it can go either way. > > also if you know me at all you'd know I would not do the "s.commit()" > inside all of the CR/U/D methods. Your methods that do a "read()" are > beginning a transaction and leaving it open implicitly (there's no > rollback() or commit() at the end, nor should there be), whereas your CRUD > methods are fully committing the transaction for individual > INSERT/UPDATE/DELETE statements, and this inconsistency and the difficulty > it introduces in what happens when multiple CRUDService methods are called > sequentially will eventually lead to architectural problems. > > > > > from pyramid.request import Request > from crudites.models.meta import Base > from sqlalchemy.orm.session import Session > from uuid import UUID > from typing import List, Tuple, Dict > from crudites.services.id_service import IDService > > > class CRUDService: > '''TODO: docstring''' > > @staticmethod > def create(cls: Base, s: Session) -> bool: > unique_id, public_id = IDService.create_db_ids() > cls.unique_id=unique_id > cls.public_id=public_id > s.add(cls) > s.commit() > return True > > @staticmethod > def read_by_pkid(cls: Base, s: Session, id: int) -> Base: > q = s.query(cls).get(id) > return q > > @staticmethod > def read_by_unique_id(cls: Base, s: Session, id: UUID) -> Base: > q = s.query(cls).filter_by(unique_id=id).first() > return q > > @staticmethod > def read_by_public_id(cls: Base, s: Session, id: str) -> Base: > q = s.query(cls).filter_by(public_id=id).first() > return q > > @staticmethod > def read_all(cls: Base, s: Session, filters: Tuple=None) -> List[Base]: > if not filters: > filters = (cls.is_deleted==False,) > q = s.query(cls).filter(*filters).all() > return q > > @staticmethod > def update(cls: Base, s: Session) -> bool: > s.add(cls) > s.commit() > return True > > @staticmethod > def delete(cls: Base, s: Session)-> bool: > cls.is_deleted=True > s.add(cls) > s.commit() > return True > > > > > Thanks, > -andrew > > > -- > SQLAlchemy - > The Python SQL Toolkit and Object Relational Mapper > > http://www.sqlalchemy.org/ > > To post example code, please provide an MCVE: Minimal, Complete, and > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > description. > --- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to sqlal...@googlegroups.com <javascript:>. > To post to this group, send email to sqlal...@googlegroups.com > <javascript:>. > Visit this group at https://groups.google.com/group/sqlalchemy. > To view this discussion on the web visit > https://groups.google.com/d/msgid/sqlalchemy/da43b503-1e7a-4a0e-bd32-619d96163c1c%40googlegroups.com > > <https://groups.google.com/d/msgid/sqlalchemy/da43b503-1e7a-4a0e-bd32-619d96163c1c%40googlegroups.com?utm_medium=email&utm_source=footer> > . > For more options, visit https://groups.google.com/d/optout. > > > -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. To view this discussion on the web visit https://groups.google.com/d/msgid/sqlalchemy/8de1508c-8d98-4e88-b838-d886dc10a9ef%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.