Usage

Service provider must be a callable with none or one argument, which will be the current ServiceContainerTransaction.

Transaction is used to cache service instances. If you don't use transaction, service container will call
service provider each time you retrieve service instance from ServiceContainer.

Note: If you don't create explicit transaction, an implicit throw-away transaction will be created for retrieval
of service instance - that comes into play when resolving sub-dependencies (see lower).

sc.foo # calls service provider for foo and returns the result (service instance).
sc.foo # calls service provider again

To create explicit transaction, use ServiceContainer as a context manager. Use transaction to wrap part of your
code that should reuse the same service instances - eg. handling of one request (if configuration may change between
them).

Note: Transactions have the same interface as ServiceContainer itself, so in place of ServiceContainer you can
pass ServiceContainerTransaction as well. In multithreaded environment each thread must use it's own transaction though.

with sc as sct:
sct.foo # calls foo_provider and returns the result (service instance).
sct.foo # returns the cached service instance.
sc.foo # direct access to `sc` circumvents transaction and creates a new instance.
with sc as sct:
sct.foo # new transaction doesn't have service instance cached, so new instance will be created again.

Transactions may be nested. For that case, transaction inherits cached instances and set params from parent transaction,
newly set params (params can be only added, not changed) or created instances then influence only that transaction and
its sub-transactions.