question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Optional dependencies

See original GitHub issue

I am trying to create a container that optionally takes a dependency, and otherwise provides a value derived from another provider. The (IMO) hacky solution I have so far is a custom provider which either provides a Callable, or a default value if the callable has an error. Then I use this with the Callable being the dependency provider.

My questions are (1) is there a better way? and (2) even using this method, DefaultCallable defined below seems like a hack – how can I improve?


T = TypeVar("T")

class DefaultCallable(providers.Provider):

    __slots__ = ("_callable", "_default")

    def __init__(
        self, callable: Callable[..., T], default: T, *args, **kwargs
    ):
        self._default = default
        self._callable = providers.Callable(callable, *args, **kwargs)

        super().__init__()

    def __deepcopy__(self, memo):
        copied = memo.get(id(self))
        if copied is not None:
            return copied

        # TODO: type?
        copied = self.__class__(
            cast(Callable[..., T], self._callable.provides),
            providers.deepcopy(self._default, memo),
            *providers.deepcopy(self._callable.args, memo),
            **providers.deepcopy(self._callable.kwargs, memo),
        )
        self._copy_overridings(copied, memo)
        return copied

    def _provide(self, args, kwargs):
        try:
            return self._callable(*args, **kwargs)
        except Exception:
            # TODO: why do we need to check if is provider?
            # type?
            if getattr(cast(Any, self._default), "__IS_PROVIDER__", False):
                return cast(Any, self._default)()
            else:
                return self._default

# Used like

class Foo(containers.DeclarativeContainer):

    #: specify dv for pattern discovery (optional)
    dv_in: Provider[xr.DataArray] = providers.Dependency(
        instance_of=xr.DataArray
    )

    #: dv for pattern discovery (specified or default)
    dv: Provider[xr.DataArray] = DefaultCallable(
        # cast(Callable[..., xr.DataArray], dv_in), type??
        cast(Any, dv_in),
        problem.training.provided["dv"],
    )

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:12 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
rmk135commented, Dec 15, 2020

Hey @shaunc , check this sample:

from dependency_injector import containers, providers


class DataArray:
    ...


class DefaultDataArray(DataArray):
    ...


class ExtraDataArray(DataArray):
    ...


class DependencyWithDefault(providers.Dependency):

    def __init__(self, instance_of=object, default=None):
        super().__init__(instance_of)
        if default:
            self.override(default)


class Foo(containers.DeclarativeContainer):

    problem_training = providers.Dict({"dv": DefaultDataArray()})

    dv_in: providers.Provider[DataArray] = DependencyWithDefault(
        instance_of=DataArray,
        default=problem_training.provided["dv"],
    )


if __name__ == '__main__':
    container_with_default = Foo()
    default_dv_in = container_with_default.dv_in()

    container_with_extra = Foo(dv_in=ExtraDataArray())
    extra_dv_in = container_with_extra.dv_in()

    assert isinstance(default_dv_in, DefaultDataArray)
    assert isinstance(extra_dv_in, ExtraDataArray)

If I got it right, the cornerstone idea is to have a default provider for the dependency. If so, the easiest way to make it is to override the dependency provider in the declarative container.

You can achieve this by extending the Dependency provider like in the sample above, or even without that:

class Foo(containers.DeclarativeContainer):

    problem_training = providers.Dict({"dv": DefaultDataArray()})

    dv_in: providers.Provider[DataArray] = providers.Dependency(instance_of=DataArray)
    dv_in.override(problem_training.provided["dv"])

Appreciate your feedback.

PS: the idea of adding a default argument like in the first sample seems reasonable. I’ll add it to the backlog.

1reaction
rmk135commented, Dec 14, 2020

Hey @shaunc , will need some time to answer.

Read more comments on GitHub >

github_iconTop Results From Across the Web

What are Optional Dependencies and when should we use them
The term optional dependencies apply to dependencies that won't cause a failure during the installation of an application or project since ...
Read more >
What Are NPM's Optional Dependencies and When Should ...
Finally, optionalDependencies are dependencies that don't necessarily need to be installed. If a dependency can be used, but you would like ...
Read more >
Optional Dependency in Maven | Baeldung
In order to exclude these special dependencies from the main project, we can apply Maven's <optional> tag to them.
Read more >
Types of dependencies | Yarn
Optional dependencies are just that: optional. If they fail to install, Yarn will still say the install process was successful. This is useful...
Read more >
package.json - npm Docs
optionalDependencies. If a dependency can be used, but you would like npm to proceed if it cannot be found or fails to install,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found