将对象标记为只读

时间:2018-08-10 17:48:50

标签: python database activerecord sqlalchemy

我正在尝试在我的图书馆中实现用户权限。看起来像这样。

class api:

def get_readonly_obj_by_id(self, user_id, obj_id) -> Obj:
    self.user_can_read_obj(user_id, obj_id) 
    return self.session.query(Obj).get(obj_id)

def save():
    self.session.commit()

是否可以将此对象标记为只读?

obj = api.get_readonly_obj_by_id(user_id, obj_id)
obj.name = 'newName'
obj.user = new_user
api.save() 

因此,使用save方法进行的所有更改都不会影响数据库中的该对象。

然后我将创建此方法。

def get_write_obj_by_id(self, user_id, obj_id) -> Obj:
    self.user_can_write_obj(user_id, obj_id)
    return self.session.query(Obj).get(obj_id)

这将使我摆脱update_obj方法。

def update_obj(self, user_id, obj_id, args: dict):
    self.user_can_write_obj(user_id=user_id, obj_id=obj_id)
    self.session.query(Obj).filter_by(id=obj_id).update(args)
    return self.session.query(Obj).get(obj_id)

,并允许像这样直接编辑对象

obj.name = 'newname' # if i received this object with get_writeble_obj method
api.save() 

使用ActiveRecord方法创建的模型。

1 个答案:

答案 0 :(得分:2)

在Python中,保护值免受意外突变相对容易,但几乎不可能保护它们免受故意突变。


如果您只是想让obj.name = 'NewName'产生TypeError,这很容易:

class MyObject:
    def __setattr__(self, name, value):
        if self._frozen:
            raise TypeError(f"frozen '{type(self).__name__}' objects do not support assignment")

当然,您首先需要使用普通的self.name = value机制来分配值。通常,您可以通过调用super().__setattr__或直接转到self.__dict__[name]来执行此操作,并且通常您希望使用__new__方法而不是__init__方法来执行此操作。但是,如果您小心一点,也可以在self._frozen = True之前这样做。

如果需要更大的灵活性,只需冻结一些特定的属性,而其他属性仍可分配,则可以使用描述符-stdlib的@property不带setter可以很好地解决此问题。如果还希望防止创建新属性,则仍然需要__setattr__-或可能要考虑使用__slots__


因此,要构造一个对象,您可以执行以下操作:

class Freezable:
    def __new__(cls, *args, frozen=False, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
        super().__setattr__(obj, '_frozen', frozen)
        return obj
    def __setattr__(self, name, value):
        if self._frozen:
            raise TypeError(f"frozen '{type(self).__name__}' objects do not support assignment")
        super().__setattr__(name, value)

现在,您可以将所有类变成Freezable的子类。他们需要通过在frozen中忽略__init__构造参数来处理super并将其显式传递给__new__中的__new__,如果其中任何一个需要自定义{{ 1}},但他们可能不会)。

然后,您的get_readonly_obj_by_id会执行以下操作:

return Whatever(user_id, obj_id, frozen=True)

但这不会阻止某人绕开限制。无论您在__setattr__中做什么,他们都可以直接做。或者,如果您使用@property将值存储在其他位置,则它们只能将其分配给其他位置。更不用说他们可以只设置obj,_frozen = False。 (您可以使它们再次跳过相同的箍,但这并不能使其变得更加安全。)

如果您还有其他受 保护的存储,则可以始终将对象更改为该存储的某种代理,而不必直接保存值,但这非常复杂-它依赖于您已经建立了某种受保护的商店;这不是首先构建一个的方法。


您的数据库大概是 就是这样一个受保护的存储。但是,您不想通过数据库代理每个属性的查找和分配-实际上,您根本不希望在save之前写任何东西。

但这意味着混合方法可能很好。

使用__setattr___frozen的“成年人同意”保护措施,以防止人们意外地将不允许存储的对象进行变异。

如果它们落后于您并且找到了一种无论如何都可以使对象变异的方法,则当他们调用save时,您可以阻止更新(或者数据库可以自动为您执行更新)并给他们提供例外。