Skip to content

Commit

Permalink
Update docs (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
yakimka authored Jan 5, 2025
1 parent fe3e92d commit 24fd44a
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 49 deletions.
7 changes: 5 additions & 2 deletions docs/knownissues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Receiving a coroutine object instead of the actual value

If you are trying to resolve async dependencies in sync functions, you will receive a coroutine object.
For regular dependencies, this is intended behavior, so only use async dependencies in async functions.
However, if your dependency uses a scope inherited from :class:`picodi.ManualScope`,
you can use :func:`picodi.init_dependencies([my_dependency])` on app startup to resolve dependencies,
However, if your dependency (e.g. ``my_dependency``) uses a :class:`picodi.SingletonScope` scope,
you can call :func:`picodi.init_dependencies([my_dependency])` on app startup to resolve dependencies,
and then Picodi will use cached values, even in sync functions.

flake8-bugbear throws "B008 Do not perform function calls in argument defaults"
Expand Down Expand Up @@ -39,3 +39,6 @@ Add ``await shutdown_dependencies()`` at the end of your tests.
async def _setup_picodi():
yield
await picodi.shutdown_dependencies()


Or use integration with ``pytest-asyncio``, more details in :doc:`testing` section.
68 changes: 24 additions & 44 deletions docs/scopes.rst
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
Scopes
======

Resolving dependency values and injecting them is a good start, but it's not enough.
We need to be able to control the lifetime of the objects we create. This is where scopes come in.
While resolving dependency values and injecting them is a good starting point,
it's insufficient on its own.
We also need control over the lifecycle of the objects we create.
This is where scopes come in.

Picodi has two types of scopes: auto and manual. Lifecycle of auto scopes is managed
by the Picodi itself, auto-scopes will be initialized and closed automatically.
Manual scopes initialized on first injection or when :func:`picodi.init_dependencies`
is called by the user and closed when :func:`picodi.shutdown_dependencies` is called.
Picodi has two types of scopes: auto and manual. The lifecycle of auto-scopes is managed
by Picodi itself. Auto-scopes are initialized and closed automatically.
Manual scopes are initialized on the first injection or when :func:`picodi.init_dependencies`
is explicitly called by the user and closed when :func:`picodi.shutdown_dependencies` is called.

To set the scope for a dependency, you can use the :func:`picodi.dependency` decorator
with the ``scope_class`` argument. Example:
Use the :func:`picodi.dependency` decorator with the ``scope_class`` argument
to set a dependency's scope. Example:

.. code-block:: python
Expand All @@ -27,26 +29,25 @@ Built-in scopes
NullScope
*********

By default, all dependencies are created with the :class:`picodi.NullScope` scope.
By default, dependencies use the :class:`picodi.NullScope` scope creating
a new instance every time they are injected.
This means that a new instance is created every time the dependency is injected
and closed immediately after root injection is done.

SingletonScope
**************

The :class:`picodi.SingletonScope` scope creates a single instance of the dependency
and reuses it every time the dependency is injected. The instance is created when the
dependency is first injected.
The :class:`picodi.SingletonScope` creates a single instance of the dependency.
This instance is reused every time the dependency is injected.

``SingletonScope`` is the manual scope, so you need to call :func:`picodi.shutdown_dependencies`
``SingletonScope`` is a manual scope, so you need to call :func:`picodi.shutdown_dependencies`
manually. Usually you want to call it when your application is shutting down.

ContextVarScope
***************

The :class:`picodi.ContextVarScope` uses the :class:`python:contextvars.ContextVar`
to store the instance. The instance is created when the
dependency is first injected.
The :class:`picodi.ContextVarScope` relies on :class:`python:contextvars.ContextVar`
to store instances.

``ContextVarScope`` is the manual scope, so you need to call :func:`picodi.shutdown_dependencies`
with ``scope_class=ContextVarScope`` manually.
Expand All @@ -69,32 +70,12 @@ You can use it as a reference:
.. literalinclude:: ../picodi/_scopes.py
:pyobject: SingletonScope

Lifecycle of manual scopes
--------------------------

You can manually initialize your dependencies by calling :func:`picodi.init_dependencies`
and pass the dependencies you want to initialize. Example:

.. code-block:: python
from picodi import ManualScope, SingletonScope, init_dependencies, dependency
@dependency(scope_class=SingletonScope)
def my_dependency():
return "my dependency"
init_dependencies(dependencies=[my_dependency])
Also, you need to manually close your dependencies by calling
:func:`picodi.shutdown_dependencies`.

Injecting async dependencies in sync dependants
***********************************************
-----------------------------------------------

One even more useful feature is that if you manually initialize your async dependencies
you can use them in sync injections. While the values is stored in the context of the
Another powerful feature is the ability to use manually initialized async dependencies
in synchronous injections. While the values is stored in the context of the
scope you can inject it in sync code. Example:

.. code-block:: python
Expand All @@ -115,9 +96,8 @@ scope you can inject it in sync code. Example:
async def main():
await init_dependencies(
[get_async_dependency]
) # Try to comment this line and see what happens
# Try to comment this line below and see what happens
await init_dependencies([get_async_dependency])
print(my_sync_service())
Expand All @@ -132,8 +112,8 @@ it's initialized on startup, while your app is running you can inject it in sync
``lifespan`` decorator
***********************

You can use the :func:`picodi.helpers.lifespan` decorator manage lifecycle of your dependencies.
It's convenient for using with workers or cli commands.
You can use the :func:`picodi.helpers.lifespan` decorator to manage the lifecycle
of your dependencies. It's convenient for using with workers or cli commands.

.. testcode::

Expand Down
6 changes: 3 additions & 3 deletions docs/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ Picodi's Lifespan in Tests
For effective testing, it’s essential to maintain a clean environment for each test.
Picodi's scopes can introduce issues if not managed properly. To prevent this,
ensure that dependencies are properly torn down after each test.
In this example, we showcase example of pseudo framework,
but you can adapt it to your testing framework.
The following example demonstrates a pseudo-framework implementation,
which you can adapt to suit your specific testing framework.

.. testcode::

Expand Down Expand Up @@ -119,7 +119,7 @@ To use the ``_pytest_asyncio`` plugin, you need to install the
Lifespan
********

By default, Picodi will automatically call :func:`picodi.shutdown_dependencies`
Picodi will automatically call :func:`picodi.shutdown_dependencies`
and make additional cleanups after each test.

If you need to call :func:`picodi.init_dependencies` - you can use marker.
Expand Down

0 comments on commit 24fd44a

Please sign in to comment.