ets-labs / python-dependency-injector

Dependency injection framework for Python
https://python-dependency-injector.ets-labs.org/
BSD 3-Clause "New" or "Revised" License
3.98k stars 306 forks source link

Cached Value #820

Open str-it opened 1 month ago

str-it commented 1 month ago

I want to have a Singleton which functions as a cache for a method call.

I want the field file_content in my container be initialized one time by calling a given method (reader.read). From then on always that result should be returned instead of calling the method again.

I have added a working code example below. Is there any better way? Maybe it might be useful if it was rewritten as a new Provider?

from pathlib import Path
from dependency_injector import containers, providers

class Reader:
    def read(self, filepath: Path, *args, **kwargs) -> str:
        print('read file')
        print('args:', args)
        print('kwargs:', kwargs)
        print()
        return filepath.read_text('utf-8')

SingletonAsCache = lambda bound_method, *args, **kwargs: bound_method(*args, **kwargs)

class MyContainer(containers.DeclarativeContainer):
    reader = providers.Factory(Reader)

    file_content = providers.Singleton(SingletonAsCache, reader.provided.read)

container = MyContainer()

def print_first_line():
    c: str = container.file_content(Path(__file__), 'any arg', any_kwarg='any_kwarg_value')
    print('first line:', c.splitlines()[0])

print_first_line()
print_first_line()
print_first_line()

Output:

read file
args: ('any arg',)
kwargs: {'any_kwarg': 'any_kwarg_value'}

first line: from pathlib import Path
first line: from pathlib import Path
first line: from pathlib import Path
nightblure commented 1 week ago

@str-it hi!

I simplified your code which you provided above and demonstrate new version of the code, as well as the output from the console, which shows that the instance of the Reader class is indeed a singleton.

code:

from pathlib import Path
from dependency_injector import containers, providers

class Reader:
    def read(self, filepath: Path, *args, **kwargs) -> str:
        print('read file')
        print('args:', args)
        print('kwargs:', kwargs)
        print()
        return filepath.read_text('utf-8')

class MyContainer(containers.DeclarativeContainer):
    reader = providers.Singleton(Reader)

def print_first_line(reader: Reader):
    print(f'reader instance id: {id(reader)}')
    c: str = reader.read(Path(__file__), 'any arg', any_kwarg='any_kwarg_value')
    print('first line:', c.splitlines()[0])

def main():
    container = MyContainer()

    for _ in range(3):
        reader = container.reader()
        print_first_line(reader)

if __name__ == '__main__':
    main()

output:

reader instance id: 4418761872
read file
args: ('any arg',)
kwargs: {'any_kwarg': 'any_kwarg_value'}

first line: from pathlib import Path
reader instance id: 4418761872
read file
args: ('any arg',)
kwargs: {'any_kwarg': 'any_kwarg_value'}

first line: from pathlib import Path
reader instance id: 4418761872
read file
args: ('any arg',)
kwargs: {'any_kwarg': 'any_kwarg_value'}

first line: from pathlib import Path

Process finished with exit code 0