Two things up front
1- I hate how this "public" api is presented to us
2- I'm tired of answering the same question over and over on IRC so
for now this is how things go I'll close with what I think we should
do for tg2.1
Keep in mind they are a lot of "inconsistencies" in the API and some
weird design choices that a limiting it by several factors.
So first quickstart a project and paste this into a template.
You will find the first error (1). tg.identity is None when you are
not logged in, this pulls from repoze.who and I don't think it's such
a bad idea as it should be None when you have no user. But it's
horrible for those cases where we need the user and/or group object.
The next error (2) is how clumsy the
tg.predicates.is_anonymous().is_met(tg.request.environ) transformation
is implemented, I won't go over this again.
http://trac.turbogears.org/ticket/2205 Draw your own conclusions.
But at least we have the more decent form tg.predicates.is_anonymous()
at least for now.
<body>
<p py:if='tg.predicates.is_anonymous().is_met(tg.request.environ)'>Anonymous
</p>
<p> The methods below work due to a flag set by TG </p>
<p py:if='tg.predicates.is_anonymous()'>Anonymous </p>
<p py:if='tg.predicates.not_anonymous()'>Not Anonymous </p>
<h1>WARNING: methods below will only work if you are logged in! </h1>
<h2> This is due to tg.identity being None if no auth is present </h2>
<h3> From repoze.who </h3>
<div>User Object: ${tg.identity['repoze.who.userid']}</div>
<h3>From repoze.what</h3>
<div>User Object: ${tg.identity['user']}</div>
<div>Groups List of Strings: ${tg.identity['groups']}</div>
<div>Groups List of Objects: ${tg.identity['user'].groups}</div>
<div>Permissions List of Strings: ${tg.identity['permissions']}</div>
<div>Permissions List of Objects: ${tg.identity['user'].permissions}</div>
<div>First Group: ${tg.identity['groups'][0]}</div>
<div>First Permission: ${tg.identity['permissions'][0]}</div>
<h3> These are real object </h3>
<div>User Object: ${str(type(tg.identity['user']))}</div>
<div>Groups List Of Strings: ${str(type(tg.identity['groups']))}</div>
<div>Groups List Of Objects: ${str(type(tg.identity['user'].groups))}</div>
<div>Groups First Object: ${str(type(tg.identity['user'].groups[0]))}</div>
<div>Permissions List Of String:
${str(type(tg.identity['permissions']))}</div>
<div>Permissions List Of Objects:
${str(type(tg.identity['user'].permissions))}</div>
</body>
So that was a bunch, how about doing the same thing in the controller?
well it's totally different.
@expose('identitytest.templates.index')
def index(self):
"""Handle the front-page."""
from repoze.what.predicates import has_permission
from tg import request
yes_or_no = has_permission('manage').is_met(request.environ)
print yes_or_no # outputs = the real "boolean object"
yes_or_no = has_permission('manage')
print yes_or_no # outputs =
<repoze.what.predicates.has_permission object at 0xa63666c>
#Why? don't know r.what works that way.
#there is a flag you can set but it's not set at the
controller level, need to ask Gustavo
identity = request.environ.get('repoze.who.identity')
# from here you can do everything in the template example as
tg.identity is the equivalent
return dict()
As you can tell there is no "short form" (3) and you need to import
items from 2 classes (4) and in order to get the rather simply
tg.identity variable from the templates you get a really weird call to
the request.environ.
So bottom line that is how things are now, if someone wants to
transform this into a tutorial be my guest. What I really want to do
is provide a sane API to fix 1,2,3,4 and probably others what you may
have. I'll leave the fix for another email, too tired right now.
Here we go once again...
It's not "how repoze.what works". It's all about how Python works.
In Python, an object can act as a boolean if its __nonzero__/__bool__ method
is set accordingly. For TG2 users, a horrible error-prone monkey-patch has
been implemented and enabled by default by popular demand:
http://trac.turbogears.org/ticket/2205
With it, the __non_zero__ method is set on every predicate, so if you want to
get its truth value, *Python* *mandates* that you use bool() *unless* it's in
the context of a boolean evaluation:
"""
yes_or_not = bool(has_permission('manage'))
if has_permission('manage'): # <-- equals: "bool(has_permission('manage'))"
do_something()
"""
The "officially supported and recommended" (aka "my") way to do this is 1)
disable that monkey-patch and 2) Use the is_met()/not_met() functions
provided by repoze.what-pylons -- they do return a bool:
"""
yes_or_not = is_met(has_permission('manage'))
if is_met(has_permission('manage')):
do_something()
"""
You'd write a few more characters, but it's safer.
Noone should ever expect an instance of a class (e.g., is_anonymous,
has_permission) to return a bool because object.__init__() calls cannot return
a value. Unless, of course, the class extends Python's bool, which can't
obviously happen in a reusable software like repoze.what.
So it all comes down to the way Python works. *Any* attempt to override this
behavior in the core of repoze.what will result in a unportable software.
Hence repoze.what-pylons exists.
> identity = request.environ.get('repoze.who.identity')
> # from here you can do everything in the template example as
> tg.identity is the equivalent
> return dict()
>
> As you can tell there is no "short form" (3) and you need to import
> items from 2 classes (4) and in order to get the rather simply
> tg.identity variable from the templates you get a really weird call to
> the request.environ.
If you want to do it the "raw" way, indeed you have to pass the environ. But
you can use the is_met() function and make it available in the template:
<p py:if="is_met(tg.predicates.is_anonymous())">Anonymous</p>
Or if you use the monkey-patch enabled by default, you could even write:
<p py:if="tg.predicates.is_anonymous()">Anonymous</p>
As you could see, I didn't have to use the environ in the samples above.
Cheers.
--
Gustavo Narea <xri://=Gustavo>.
| Tech blog: =Gustavo/(+blog)/tech ~ About me: =Gustavo/about |
I'm sorry but this is circular logic to me. It is a monkey patch
because you decided not to enable it by default, because it's
error-prone (to monkey patch), because someone (I know this predates
r.what) decided the predicates where classes which forced the
__nonzero__ call. It all boils down to the fact that the current
internal state of the system forces ALL of those decisions on us. If
you implement it half way (aka booleanize_predicates) you are creating
several other problems (your error-phone point and the ugly API).
> With it, the __non_zero__ method is set on every predicate, so if you want to
> get its truth value, *Python* *mandates* that you use bool() *unless* it's in
> the context of a boolean evaluation:
> """
> yes_or_not = bool(has_permission('manage'))
>
> if has_permission('manage'): # <-- equals: "bool(has_permission('manage'))"
> do_something()
> """
>
> The "officially supported and recommended" (aka "my") way to do this is 1)
> disable that monkey-patch and 2) Use the is_met()/not_met() functions
> provided by repoze.what-pylons -- they do return a bool:
which gets you a horrible api.
> """
> yes_or_not = is_met(has_permission('manage'))
>
> if is_met(has_permission('manage')):
> do_something()
> """
>
> You'd write a few more characters, but it's safer.
>
how is the other way unsafe?
> Noone should ever expect an instance of a class (e.g., is_anonymous,
> has_permission) to return a bool because object.__init__() calls cannot return
> a value. Unless, of course, the class extends Python's bool, which can't
> obviously happen in a reusable software like repoze.what.
>
> So it all comes down to the way Python works. *Any* attempt to override this
> behavior in the core of repoze.what will result in a unportable software.
> Hence repoze.what-pylons exists.
>
>
>> identity = request.environ.get('repoze.who.identity')
>> # from here you can do everything in the template example as
>> tg.identity is the equivalent
>> return dict()
>>
>> As you can tell there is no "short form" (3) and you need to import
>> items from 2 classes (4) and in order to get the rather simply
>> tg.identity variable from the templates you get a really weird call to
>> the request.environ.
>
> If you want to do it the "raw" way, indeed you have to pass the environ. But
> you can use the is_met() function and make it available in the template:
> <p py:if="is_met(tg.predicates.is_anonymous())">Anonymous</p>
>
> Or if you use the monkey-patch enabled by default, you could even write:
> <p py:if="tg.predicates.is_anonymous()">Anonymous</p>
>
> As you could see, I didn't have to use the environ in the samples above.
>
ok that's one example I forgot to add, thanks :)
Jorge said:
> On Sat, Jun 13, 2009 at 9:07 AM, Gustavo Narea<m...@gustavonarea.net> wrote:
> > Hello, Jorge et al.
> >
> > Jorge said:
> >> The next error (2) is how clumsy the
> >> tg.predicates.is_anonymous().is_met(tg.request.environ) transformation
> >> is implemented, I won't go over this again.
> >> http://trac.turbogears.org/ticket/2205 Draw your own conclusions.
> >>
> >> But at least we have the more decent form tg.predicates.is_anonymous()
> >> at least for now.
> >>
> >> <body>
> >> <p
> >> py:if='tg.predicates.is_anonymous().is_met(tg.request.environ)'>Anonymou
> >>s </p>
As I've said before, the problem is not that __non_zero__ is set dynamically
(i.e., monkey-patched). The problem is that __nonzero__ should not be set.
From a generic perspective, not specifically about repoze.what:
The only safe way to evaluate a condition represented by an object that is
shared among threads, is to make this object stateless and evaluate it by
passing other objects which represent the context of the evaluation.
If you want to skip the step where you pass the context, so you could pass it
implicitly to save code (i.e., with thread-locals being the only way to do so,
AFAIK), you'll end up with a buggy software: This object won't be stateless
anymore, in spite of being used in many threads, which is absolutely
unreliable.
> > With it, the __non_zero__ method is set on every predicate, so if you
> > want to get its truth value, Python mandates that you use bool() unless
> > it's in the context of a boolean evaluation:
> > """
> > yes_or_not = bool(has_permission('manage'))
> >
> > if has_permission('manage'): # <-- equals:
> > "bool(has_permission('manage'))" do_something()
> > """
> >
> > The "officially supported and recommended" (aka "my") way to do this is
> > 1) disable that monkey-patch and 2) Use the is_met()/not_met() functions
> > provided by repoze.what-pylons -- they do return a bool:
>
> which gets you a horrible api.
"Para gustos, colores".
If it's a horrible API for you, I won't discuss it. It may a matter of taste
here, I think. I don't think it is, though.
> > """
> > yes_or_not = is_met(has_permission('manage'))
> >
> > if is_met(has_permission('manage')):
> > do_something()
> > """
> >
> > You'd write a few more characters, but it's safer.
>
> how is the other way unsafe?
I've just explained this above.
No, I meant __nonzero__, however it's defined, is wrong. [1]
> > If you want to skip the step where you pass the context, so you could
> > pass it implicitly to save code (i.e., with thread-locals being the only
> > way to do so, AFAIK), you'll end up with a buggy software: This object
> > won't be stateless anymore, in spite of being used in many threads, which
> > is absolutely unreliable.
>
> how are thread locals buggy? if you simply pass the environ to each
> call won't that fix all your issues with it? make each predicate take
> the environ as first argument and make them functions that return a
> boolean or raises NotAutorizedException. What is the flaw there?
Noone but you said that thread locals were buggy. And the flaw is in the whole
proposal, because you cannot use functions, as I've told you in another
thread.
Cheers.
[1]
http://groups.google.com/group/turbogears/browse_thread/thread/f35ef3d347793682
It's the exact same thing: authorize() will receive a True or a False, and it
cannot do anything useful with it.
Yeah, in that case, it'd certainly work. But to be honest I'd rather something
simpler, like this (for example):
http://groups.google.com/group/turbogears-trunk/browse_thread/thread/385f28ff4e880593