Simpler, more concise rule declarations with R objects
bridgekeeper.rules should expose a R class, with an API similar to django.db.models.Q.
This class would take keyword arguments, with either constants, functions that take a user, or other Bridgekeeper rules as values.
This class should take keyword arguments, where keys are attribute names (traversing relationships with __ where applicable), and values are either database values, functions that take a user, or (where the target attribute is a relationship) another Bridgekeeper rule object.
As an example of what this might look like, here's a couple of snippets from the documentation, alongside the equivalent rule expressed in terms of R.
blue_widgets_only = Attribute('colour', matches='blue')
blue_widgets_only = R(colour='blue')
perms['shrubbery.update_shrubbery'] = Attribute('branch', lambda user: user.profile.branch)
perms['shrubbery.update_shrubbery'] = R(branch=lambda user: user.profile.branch)
perms['shrubbery.update_shrubbery'] = Relation(
'branch',
models.Branch,
# This rule gets checked against the branch object, not the shrubbery
Attribute('store', lambda user: user.profile.branch.store),
)
perms['shrubbery.update_shrubbery'] = R(branch__store=lambda user: user.profile.branch.store)
perms['foo.view_application'] = Relation(
'applicant', Applicant, perms['foo.view_applicant'])
perms['foo.view_application'] = R(applicant=perms['foo.view_applicant'])
perms['foo.view_customer'] = ManyRelation(
'agencies', Agency, Is(lambda user: user.agency))
perms['foo.view_customer'] = R(agencies=lambda user: user.agency)
perms['shrubbery.update_shrubbery'] = is_staff | (
is_shrubber & Relation(
'branch',
models.Branch,
Attribute('store', lambda user: user.profile.branch.store),
)
) | (
is_apprentice & Attribute('branch', lambda user: user.profile.branch)
)
perms['shrubbery.update_shrubbery'] = is_staff | (
is_shrubber & R(branch__store=lambda user: user.profile.branch.store)
) | (
is_apprentice & R(branch=lambda user: user.profile.branch)
)
It is worth pointing out that this work will depend on work for #3, which will also decrease the verbosity of some of the non-R examples a bit.
As an aside, lookups (__gt, __in etc) could also be supported. This could be done with each one being a rule object of its own that takes a property and a value_or_function, since they'd need to do one thing for Q lookups and another for Python-land checks, and rule objects are already a mechanism for doing that. My intention is not to do that at first though, since we haven't encountered a need for testing anything other than equality yet.