#pyramid

/

      • tiwula has quit
      • chrisw joined the channel
      • hvelarde|away is now known as hvelarde
      • jcarl43 joined the channel
      • zepolen joined the channel
      • dfee joined the channel
      • x58
        Our docs seem to be offline for me :/
      • lane_ has quit
      • Spanktar joined the channel
      • betabug
      • zepolen has quit
      • zepolen joined the channel
      • zepolen has quit
      • lane_ joined the channel
      • energizer joined the channel
      • dfee
        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
      • if you wanted to test a custom policy
      • dfee
        yeah, i guess i could just do that: https://docs.pylonsproject.org/projects/pyramid...
      • econoraptorman joined the channel
      • 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.
      • raydeo
        for sure
      • dfee
        how does the find_service function work?
      • raydeo
        I'll point you at the readme for that
      • 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