[py-dev] RFC: draft new resource management API (v1)

holger krekel holger at merlinux.eu
Wed Jun 27 18:15:54 CEST 2012

On Wed, Jun 27, 2012 at 08:43 -0600, Carl Meyer wrote:
> I like it! In particular the parametrization support by passing a list
> is a quite intuitive extension of the API.
> "atnode" seems like an opaque arg name - what's wrong with "scope"? The
> latter name seems more intuitive to me. Would this arg have a default value?

"scope" makes sense - it's just that in the current API scope is a
"class", "module", ... string.  Existing users might easily get a bit of
type clash - especially if you have a mixed funcarg/resource scenario.
Maybe "scopenode"?

The default scopenode would be the one on which you are calling
"register_factory".  So in the first documented example call:

    session.register_factory("db", createdb, scopenode=session)

the "scopenode" call would actually be superfluous.  (Sidenote: the session 
object is also a node - the root node from which all collection and item 
nodes are descendants.  Each node has a ".session" reference back to this
root node).

> In the long run, if funcarg-style is considered a useful shortcut and
> will not be deprecated, it would be nice if there were a bit more naming
> and API consistency between funcarg-style and new-style resource
> handling -- it would make them feel more aspects of one system rather
> than two different systems. I think this would really just require
> switching from pytest_funcarg__foo to pytest_resource__foo, renaming
> cached_setup to register_factory (and having it use the same API), and
> renaming getfuncargvalue to getresource. Of course I don't know whether
> this consistency is really worth the backwards-compatibility/deprecation
> hassles.

* getresource/getfuncargvalue: makes sense to me to go for advertising and
  documenting getresource() instead of getfuncargvalue() and keeping
  the latter as an alias with or without deprecation.

* addfinalizer would remain unmodified - it's just that the "request"
  object passed to funcarg-factories adds finalizers with test function 
  invocation scope, whereas node.addfinalizer() does it for the respective
  node scope (so e.g. called from a Class node it would register a per-class 

* cached_setup: i hope that we do not need to offer this method anymore
  other than for compatibility.  It's internal caching-key is not easy 
  to explain and more than once users have stumbled about understanding it.
  cached_setup is required as long as pytest_funcarg__ factories are called
  _each_ time a resource is requested. (By contrast the new getresource()
  only triggers a factory call once for the registered scope - thus
  the factory implementation itself does not need to care for caching).

  Note that register_factory is a different beast than cached_setup: 
  it does not create a value, just registers a factory. So i don't see 
  how we can unify this.

As to a possible resource-factory auto-discovery, i can imagine it to
work with introducing a marker::

    # example content in a test module or in a conftest.py file

    @pytest.mark.resourcefactory("db", scope=pytest.Class)
    def myfactory(name, node):
        # factory called once per each requesting class (methods
        # on this class will share the returned value)

this declaration would trigger a register_factory("db", myfactory) call. 
If we want to extend this to parametrization (multiple db factories)
we probably need something like this::

    @pytest.mark.resourcefactory("db", scope=pytest.Class, multi=True)
    def make_db_factories(name, node):
        factoryfuncs = [compute list of factory funcs]
        return factoryfuncs

This would be called at collection time and the scope and the number
of to-be-created values would be known in advance.  It's basically
equivalent to a classnode.register_factory([list of factory funcs]) call.
(we could auto-magically recognize yield-generating functions but i'd
like to avoid it).

To go the full circle, the signature of factory functions could rather
accept a "request" object instead of (name, node). Actually today, a 
request object has this internal state anyway. pytest_funcarg__ would thus 
only look slighly special in that it skips the marker and has a fixed scope
of "pytest.Function".

Hope this thought train makes some sense :)

More information about the Pytest-dev mailing list