what is the practical value of `testing_securitypolicy` when used wiht `has_permission`? request.has_permission will just return the `permissive` value of `testing_securitypolicy`, it won't actually process the `__acl__`.
chrisw joined the channel
i just don't know how i should be testing __acl__s
raydeo_
dfee: testing_securitypolicy if I recall is just to set the effective principals
oh sorry it also mocks the authz policy
raydeo_ is now known as raydeo
raydeo
so yeah anyway the point is that if you use it then you're saying you want to override your authn/authz policies and just set a static value for this test
dfee
yep, i understand that after toying with it for a couple min.
but i'm at a loss for testing my actual ACL / authz without building my entire app...
or maybe i should just use testing_securitypolicy to test the underlying code, and then have integration tests (with the full fledged app) that test ACL
raydeo
for testing the acl you can just make a dummy request and a context object
then you just do request.has_permission('view', context=...)
where you set request.effective_principals = [ ... ]
anyway it really depends on what you want to test specifically :-)
dfee
but that approach still requires setting an authn and authz policy on a conifg object / registry that is pushed onto the stack, right?
raydeo
sure
of course you could pass these args directly to an instance of your authz policy's permits() fn
raydeo: i guess as i still need to set up my database, add it to the request, and do that for other services like redis and elasticsearch, is it more practical to just skip straight to integration testing rather than duplicating the setup procedure i already have in my app entrypoint?
hvelarde has quit
hvelarde joined the channel
raydeo
dfee: to get a context object you mean?
at some point you're mocking too many things and aren't really testing anything... you have to find that balance
dfee
raydeo: my situation is a bit more involved, unfortunately. but practically speaking, if my function requires a sqlalchemy session on the request, then i'm adding assembling that construct in both a pytest fixture and my app's `main` function.
so if i have functions that require a dbsession, and a redis client, then it's starting to look like i'm just re-assembling the configuration the application provides.
raydeo
sure, if you programmed your code to require those things then it is what it is :-)
this is where things like programming to an interface to make replacing them easy can be helpful in writing tests
dfee
raydeo: i'd initially not programmed my code to require a request object with reified properties on them, but i wanted to take advantage of request.has_permission inside my function.
and unfortunately, it seems that if you want to take advantage of pyramid's ACL, the request object has to be the center of everything (e.g. the authz policy takes a callback that receives the request...)
raydeo
sure! you can hide that stuff behind your own abstractions but fundamentally the request is what pyramid uses to identify a user
note that the authz policy does not require a request
zepolen joined the channel
zepolen has quit
dfee
raydeo: i think i missed that. but that's pretty excellent (not requiring a request).
i'm kind of hoping to use pyramid "this time" as a sort of app foundation, with the web-component being the most limited part.
zepolen joined the channel
i have like three endpoints (largely due to using GraphQL), so a heavy reliance on the request object doesn't serve me too well.
raydeo
for sure
I'm actually using pyramid with graphene right now
dfee
exactly
raydeo
I use the request as the context for a graphql query
dfee
like you literally supply `schema.execute(..., context_value=request)`?
raydeo
yep
so my data loaders and any other services are tacked onto it via request.find_service from pyramid_services
I wouldn't say it's ideal, but it doesn't suck
dfee
i've moved the implementation of my code to "controllers", classes that coordinate actions between postgres/sqlalchemy, redis, and elasticsearch.
raydeo
for the most part the graphql types/mutations don't know they're dealing with a request object... they just do stuff like context.find_service(...)
dfee
so are you doing authz then? my approach is just using a decorator to check permissions on resolve_ funcs.
zepolen has quit
knowing that `info.context.has_permission` would then be the pyramid request obj.
raydeo
yeah we have decorators that use the context but they aren't very object-level right now
like... most permissions are just on the user themselves and then the resolve func does some more fine-grained checks unfortunately
dfee
gotcha
raydeo
so the acls are more just some simple roles and then a lot of security is currently done imperatively
I haven't had time to work out a better approach there
in theory I think it'd be like putting an acl on the type itself
dfee
raydeo: that's pretty close to what i've done. my resolve_funcs actually call into controller classes that have __acl__s on them.
raydeo
I haven't noticed a big down side yet to using the request as the graphql context tho
dfee
well, when you test your resolve_funcs, you are back in the same position of passing in a "fully-loaded" request object. so how much of that is really unit-testing?
raydeo
well like I said they don't really know it's a request... they expect certain things to be on the context though for sure
so you have to define that contract
dfee
i think i see what you're saying
raydeo
like I said all my database logic and stuff is behind services so types just do stuff like svc = context.find_service(ExampleService) then svc.get_all_things(...)
so it's easy to replace ExampleService with one that doesn't use the database
zepolen joined the channel
dfee
so your service is roughly equivalent to my controller.
the idea is that the services are instantiated per-request and the user of the service doesn't need to know how that was done
teix has quit
dstufft
pyramid_serivces is great
recommend
dfee
raydeo: i might actually replace my controller logic with services.
raydeo
awesome :-)
atomekk has quit
atomekk joined the channel
atomekk has quit
atomekk joined the channel
slav0nic joined the channel
Ergo joined the channel
Ergo
hi, StringListSchema(missing=()) in colander - is that safe to pass [] or {} as a missing value?
or do I need to use deferreds everywhere?
raydeo
depends if you every mutate the value
short answer is that it's probably a bad idea
sawdog joined the channel
Ergo
yeah, thats what I though
ok... indeed i checked() id and im getting same object
checked id()
dfee
raydeo: i noticed that i can provide register_service_factory an Interface or ... anything ... and an interface will be generated. any reason I should prefer to use zope.interfaces rather than just using ABC? https://github.com/mmerickel/pyramid_services/b...
raydeo
no
the only caveat is that the class-based stuff will not work well with a class hierarchy
dfee
ok, because i'm a lot more comfortable with ABC than i am with zope :)
hmm, don't understand what you mean by that
raydeo
the class-based support is fairly hacky unfortunately but basically it means that if you register class A as a service type then you shouldn't register any subclasses of A as a different service type
like... don't expect to do class B(A) and register a "more specific" service for class B
it's a convenience so you can use the class type in register_service and find_service but if you need any complex type hierarchies then you should use z.i
dfee
so it's important to think of it as 1:1 mapping between an ABC and it's implementor - approximating the same relationship z.i shares with an implementor. basically, subclassing a concrete type shouldn't be done
raydeo
yeah suffice it to say the class version is a hack and is not nearly as robust as the z.i version
but I really wanted it to at least sort of work in my own apps so I added it :p
Belxjander has quit
Belxjander joined the channel
Belxjander has quit
Belxjander joined the channel
Belxjander has quit
Belxjander joined the channel
dfee has quit
dfee joined the channel
waveform has quit
dfee
raydeo: curious here - are your services returning sqlalchemy instances for graphene to automatically `resolve_` from? my gut is that it'd be most proper to pass back some kind of serialized stateless object, but that seems like quite a bit of overhead, too.
raydeo
not sure I follow
oh... I have my types implement a classmethod called from_model(cls, context, model)
which is responsible for converting the obj into an instance of the graphene type... and I have a helper fn to infer attrs and map between them that most types use
so my data loaders do things like GrapheneUser.from_model(context, sql_user_obj)
dfee
but your service does return a model instance, right? i'm trying to not go off the deep end here, but it seems that if i have a PersistentProductService, for example, a call to pps.create(**kwargs) returns an instance.
raydeo
yes
almost all the services have nothing to do with graphql
so they just use sqla objs
dfee
yeah i've got that, but i'm more focused on the "swapability" of services if a NonPersistentProductService were swapped in... it wouldn't really be returning a ProductModel instance
or, it could just return an instance of that model that's not bound to an engine
raydeo
yeah, sqla objects do not need to be bound
they are nice like that
of course you could define some other type via attrs that you pass around
but I don't think that is worthwhile
dfee
i hit a point where i wanted to check that i'm actually getting value out of this additional level of abstraction. if i can't truly swap in some other service, than i'm just adding complexity for the sake of complexity.
raydeo
for me the major value-add of pyramid_services is that it separates "creator" issues from "user" issues on each service
dfee
i guess the whole notion of the service-layer is a "hedge" on whether you're doing it right. this is the "in case i want to swap out databases implementations". but practically speaking, i'm not sure the service layer actually solves that, unless the two services are functionally equivalent and share a common api.
raydeo
services normally depend on other services (for example a database session), so creating the services requires passing in other services to the __init__... so defining that in one spot
and I don't want to think about that each time I want an instance to call stuff on so find_service() hides all of that crap
dfee
yep
raydeo
certainly you have to decide if you see value in that stuff or not, but it's an important "first step" in swappability
further enforcing contracts is stuff that z.i was built for