modern-python / that-depends

DI-framework, inspired by python-dependency-injector, but without wiring. Python 3.12 is supported
https://that-depends.readthedocs.io/
MIT License
133 stars 10 forks source link

as_() method from configuration provider #71

Closed Pentusha closed 2 weeks ago

Pentusha commented 3 weeks ago

First of all, I would like to thank you for creating such a great tool, which is already a worthy alternative to python-dependency-injector.

I am currently trying to migrate my old configurations from python-dependency-injector, but I’m missing the functionality of the configuration provider, specifically the .as_() method.

Perhaps you could suggest a straightforward approach or a workaround to achieve the desired behavior. Here’s what I’m trying to do:

I have a microservice architecture application built on FastAPI, where I manage the routing for the services via the servers and root_path parameters. One service may have root_path set to /service1, another to /service2, making the services available via the following links:

At the same time, I want to retain the ability to specify an empty string for root_path, which is necessary for projects that use a more monolithic approach. The desired behavior could be achieved in python-dependency-injector by properly combining the servers and root_path parameters.

Here’s how I implemented this using python-dependency-injector:

env = providers.Configuration(strict=True, pydantic_settings=[env_secrets])

servers = providers.List(
    providers.Dict(
        url=env.API_ROOT_PATH.as_(lambda x: x or '/'),
        description=env.ENV,
    ),
)

app_factory = providers.Factory(
    FastAPI,
    root_path=env.API_ROOT_PATH,
    servers=servers,
    # other parameters
)

As you can see, these two parameters are linked through one environment variable.

If the library requires improvements, I am willing to contribute. However, I would first like to confirm that there is no standard way to achieve this behavior and would appreciate any advice on implementation.

lesnik512 commented 2 weeks ago

@Pentusha Hi) I'm glad that this project is useful not only for my projects:)

I think, is quite challenging task to achieve the same behaviour because there is another approach on types in that-depends. For us it's like we don't have any proxy object for configuration management.

I think it's better to create some property on pydantic settings model, which returns default value, if it's empty:

import pydantic_settings
from that_depends import BaseContainer, providers

class Settings(pydantic_settings.BaseSettings):
    API_ROOT_PATH: str = "/some_service"

    @property
    def API_ROOT_PATH_WITH_DEFAULT(self):
        return self.API_ROOT_PATH or '/'

class DIContainer(BaseContainer):
    settings = providers.Singleton(Settings)
    settings_casted: Settings = settings.cast

    servers = providers.List(
        providers.Dict(
            url=settings_casted.API_ROOT_PATH_WITH_DEFAULT,
            description=settings_casted.ENV,
        ),
    )

    app_factory = providers.Factory(
        FastAPI,
        root_path=settings_casted.API_ROOT_PATH,
        servers=servers,
        # other parameters
    )
Pentusha commented 2 weeks ago

Thank you very much, that seems to be the appropriate workaround for me.