{"id":13458949,"url":"https://github.com/suned/serum","last_synced_at":"2025-09-29T05:30:22.387Z","repository":{"id":50207189,"uuid":"117967351","full_name":"suned/serum","owner":"suned","description":"Dependency injection framework for Python 3.6","archived":true,"fork":false,"pushed_at":"2021-06-01T22:12:15.000Z","size":177,"stargazers_count":86,"open_issues_count":6,"forks_count":7,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-12-17T01:44:57.225Z","etag":null,"topics":["configuration","convenience","dependency-injection","pycharm","python","solid"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/suned.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-18T10:22:12.000Z","updated_at":"2024-11-04T22:03:58.000Z","dependencies_parsed_at":"2022-09-13T08:12:13.854Z","dependency_job_id":null,"html_url":"https://github.com/suned/serum","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suned%2Fserum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suned%2Fserum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suned%2Fserum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suned%2Fserum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/suned","download_url":"https://codeload.github.com/suned/serum/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234594019,"owners_count":18857416,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration","convenience","dependency-injection","pycharm","python","solid"],"created_at":"2024-07-31T09:01:00.146Z","updated_at":"2025-09-29T05:30:21.708Z","avatar_url":"https://github.com/suned.png","language":"Python","funding_links":[],"categories":["Software"],"sub_categories":["Archived or unmaintained DI frameworks"],"readme":"# Build Status\n\nBuild status: \n\n[![CircleCI](https://circleci.com/gh/suned/serum.svg?style=svg)](https://circleci.com/gh/suned/serum)\n\nCode quality:\n\n[![Test Coverage](https://api.codeclimate.com/v1/badges/523bc990f4ef696aa22d/test_coverage)](https://codeclimate.com/github/suned/serum/test_coverage) \n\n[![Maintainability](https://api.codeclimate.com/v1/badges/523bc990f4ef696aa22d/maintainability)](https://codeclimate.com/github/suned/serum/maintainability)\n\n\n\n\n# Description\n`serum` is a fresh take on Dependency Injection in Python 3.\n\n`serum` is pure python and has no dependencies.\n# Installation\n```\n\u003e pip install serum\n```\n# Quickstart\n```python\nfrom serum import inject, dependency, Context\n\n\n# Classes decorated with 'dependency' are injectable types.\n@dependency \nclass Log:\n    def info(self, message: str):\n        raise NotImplementedError()\n\n\nclass SimpleLog(Log):\n    def info(self, message: str):\n        print(message)\n\n\nclass StubLog(SimpleLog):\n    def info(self, message: str):\n        pass\n\n\n@inject  # Dependencies are injected using a class decorator...\nclass NeedsLog:\n    log: Log  # ...and class level annotations...\n\n\nclass NeedsSimpleLog:\n    @inject  # ...or using a function decorator\n    def __init__(self, log: SimpleLog):\n        self.log = log \n\n\n@inject\nclass NeedsNamedDependency:\n    named_dependency: str  # class level annotations annotated with a type that is not\n                           # decorated with 'dependency' will be treated as a named\n                           # dependency\n                           \n\n# Contexts provide dependencies\nwith Context(SimpleLog, named_dependency='this name is injected!'):\n    assert isinstance(NeedsLog().log, SimpleLog)\n    assert NeedsNamedDependency().named_dependency == 'this name is injected!'\n    \n\n# Contexts will always provide the most specific \n# subtype of the requested type. This allows you to change which\n# dependencies are injected.\nwith Context(StubLog):\n    NeedsLog().log.info('Hello serum!')  # doesn't output anything\n    NeedsSimpleLog().log.info('Hello serum!')  # doesn't output anything\n```\n# Documentation\n- [`inject`](#inject)\n- [`dependency`](#dependency)\n- [`Context`](#context)\n- [`singleton`](#singleton)\n- [`mock`](#mock)\n- [`match`](#match)\n- [IPython Integration](#ipython-integration)\n\n## `inject`\n`inject` is used to decorate functions and classes in which you want to inject\ndependencies.\n```python\nfrom serum import inject, dependency\n\n@dependency\nclass MyDependency:\n    pass\n\n@inject\ndef f(dependency: MyDependency):\n    assert isinstance(dependency, MyDependency)\n\nf()\n```\nFunctions decorated with `inject` can be called as normal functions. `serum` will\nnot attempt to inject arguments given at call time.\n```python\n@inject\ndef f(dependency: MyDependency):\n    print(dependency)\n\nf('Overridden dependency')  #  outputs: Overridden dependency \n```\n`inject` will instantiate classes decorated with [`dependency`](#dependency). In\nthis way, your entire dependency graph can be specified using just `inject` and \n`dependency`.\n\nInstances of simple types and objects you want to instantiate yourself can be\ninjected using keyword arguments to [`Context`](#context).\n```python\n@inject\ndef f(dependency: str):\n    assert dependency == 'a named dependency'\n\nwith Context(dependency='a named dependency'):\n    f()\n```\n`inject` can also be used to decorate classes. \n```python\n@inject\nclass SomeClass:\n    dependency: MyDependency \n```\nThis is roughly equivalent to:\n```python\nclass SomeClass:\n    @inject\n    def __init__(self, dependency: MyDependency):\n        self.__dependency = dependency\n    \n    @property\n    def dependency(self) -\u003e MyDependency:\n        return self.__dependency\n```\nDependencies that are specified as class level annotations can be overridden\nusing key-word arguments to `__init__`\n```python\nassert SomeClass(dependency='Overridden!').dependency == 'Overridden!'\n```\n## `dependency`\nClasses decorated with `dependency` can be instantiated and injected\nby `serum`.\n```python\nfrom serum import dependency, inject\n\n@dependency\nclass Log:\n    def info(self, message):\n        print(message)\n\n\n@inject\nclass NeedsLog:\n    log: Log\n\n\nassert isinstance(NeedsLog().log, Log)\n```\n`serum` relies on being able to inject all dependencies for `dependency` decorated classes \nrecursively. To achieve this, `serum` assumes that the `__init__` method \nof `dependency` decorated classes can be called without any arguments.\nThis means that all arguments to `__init__` of `dependency` decorated classes must be injected using `inject`.\n```python\n@dependency\nclass SomeDependency:\n    def method(self):\n        pass\n\n\n@inject\n@dependency\nclass ValidDependency:  # OK!\n    some_dependency: SomeDependency\n\n    def __init__(self):\n        ...\n\n\n@dependency\nclass AlsoValidDependency:  # Also OK!\n    @inject\n    def __init__(self, some_dependency: SomeDependency):\n        ...\n\n\n@dependency\nclass InvalidDependency:\n    def __init__(self, a):\n        ...\n\n@inject\ndef f(dependency: InvalidDependency):\n    ...\n\nf()  \n# raises:\n# TypeError: __init__() missing 1 required positional argument: 'a'\n\n# The above exception was the direct cause of the following exception:\n\n# InjectionError                            Traceback (most recent call last)\n# ...\n# InjectionError: Could not instantiate dependency \u003cclass 'InvalidDependency'\u003e \n# when injecting argument \"dependency\" in \u003cfunction f at 0x10a074ea0\u003e.\n```\n\nNote that circular dependencies preventing instantiation of `dependency` decorated\nclasses leads to an error.\n```python\n@dependency\nclass AbstractA:\n    pass\n\n@dependency\nclass AbstractB:\n    pass\n\n\nclass A(AbstractA):\n\n    @inject\n    def __init__(self, b: AbstractB):\n        self.b = b\n\nclass B(AbstractB):\n    @inject\n    def __init__(self, a: AbstractA):\n        self.a = a\n\n@inject\nclass Dependent:\n    a: AbstractA\n\n\nwith Context(A, B):\n    Dependent().a  # raises: CircularDependency: Circular dependency encountered while injecting \u003cclass 'AbstractA'\u003e in \u003cB object at 0x1061e3898\u003e\n```\n## `Context`\n`Context`s provide implementations of dependencies. A `Context` will always provide the most\nspecific subtype of the requested type (in Method Resolution Order).\n```python\n@dependency\nclass Super:\n    pass\n\n\nclass Sub(Super):\n    pass\n\n@inject\nclass NeedsSuper:\n    instance: Super\n\n\nwith Context(Sub):\n    assert isinstance(NeedsSuper().instance, Sub)\n```\nIt is an error to inject a type in an `Context` that provides two or more equally specific subtypes of that type:\n```python\nclass AlsoSub(Super):\n    pass\n\n\nwith Context(Sub, AlsoSub):\n    NeedsSuper() # raises: AmbiguousDependencies: Attempt to inject type \u003cclass 'Log'\u003e with equally specific provided subtypes: \u003cclass 'MockLog'\u003e, \u003cclass 'FileLog'\u003e\n```\n`Context`s can also be used as decorators:\n```python\ncontext = Context(Sub)\n\n@context\ndef f():\n    assert isinstance(NeedsSuper().instance, Sub)\n\n``` \nYou can provide named dependencies of any type using keyword arguments.\n```python\n@inject\nclass Database:\n    connection_string: str\n    \n\nconnection_string = 'mysql+pymysql://root:my_pass@127.0.0.1:3333/my_db'\ncontext = Context(\n    connection_string=connection_string\n)\nwith context:\n    assert Database().connection_string == connection_string\n```\n`Context`s are local to each thread. This means that when using multi-threading\neach thread runs in its own context\n```python\nimport threading\n\n\n@singleton\nclass SomeSingleton:\n    pass\n\n\n@inject\ndef worker(instance: SomeSingleton):\n    print(instance)\n\nwith Context():\n    worker() # outputs: \u003cSomeSingleton object at 0x101f75470\u003e\n    threading.Thread(target=worker).start() # outputs: \u003cSomeSingleton object at 0x1035fb320\u003e\n```\n\n## `singleton`\nTo always inject the same instance of a dependency in the same `Context`, annotate your type with `singleton`.\n```python\nfrom serum import singleton\n\n\n@singleton\nclass ExpensiveObject:\n    pass\n\n\n@inject\nclass NeedsExpensiveObject:\n    expensive_instance: ExpensiveObject\n\n\ninstance1 = NeedsExpensiveObject()\ninstance2 = NeedsExpensiveObject()\nassert instance1.expensive_instance is instance2.expensive_instance\n```\nNote that `Singleton` dependencies injected in different environments \nwill not refer to the same instance.\n```python\n\nwith Context():\n    instance1 = NeedsExpensiveObject()\n\nwith Context():\n    assert instance1.expensive_instance is not NeedsExpensiveObject().expensive_instance\n```\n## `mock`\n`serum` has support for injecting `MagicMock`s from the builtin\n`unittest.mock` library in unittests using the `mock` utility\nfunction. Mocks are reset\nwhen the environment context is closed.\n```python\nfrom serum import mock\n\n@dependency\nclass SomeDependency:\n    def method(self):\n        return 'some value' \n\n@inject\nclass Dependent:\n    dependency: SomeDependency\n\n\ncontext = Context()\nwith context:\n    mock_dependency = mock(SomeDependency)\n    mock_dependency.method.return_value = 'some mocked value'\n    instance = Dependent()\n    assert instance.dependency is mock_dependency\n    assert instance.dependency.method() == 'some mocked value'\n\nwith context:\n    instance = Dependent()\n    assert instance.dependency is not mock_dependency\n    assert isinstance(instance.dependency, SomeDependency)\n```\n`mock` uses its argument to spec the injected instance of `MagicMock`. This means\nthat attempting to call methods that are not defined by the mocked `Component`\nleads to an error\n```python\nwith context:\n    mock_dependency = mock(SomeDependency)\n    mock_dependency.no_method()  # raises: AttributeError: Mock object has no attribute 'no method'\n```\nNote that `mock` will only mock requests of the\nexact type supplied as its argument, but not requests of\nmore or less specific types\n```python\nfrom unittest.mock import MagicMock\n\n@dependency\nclass Super:\n    pass\n\n\nclass Sub(Super):\n    pass\n\n\nclass SubSub(Sub):\n    pass\n\n\n@inject\nclass NeedsSuper:\n    injected: Super\n\n\n@inject\nclass NeedsSub:\n    injected: Sub\n\n\n@inject\nclass NeedsSubSub:\n    injected: SubSub\n\n\nwith Context():\n    mock(Sub)\n    needs_super = NeedsSuper()\n    needs_sub = NeedsSub()\n    needs_subsub = NeedsSubSub()\n    assert isinstance(needs_super.injected, Super)\n    assert isinstance(needs_sub.injected, MagicMock)\n    assert isinstance(needs_subsub.injected, SubSub)\n```\n## `match`\n`match` is small utility function for matching `Context` instances\nwith values of an environment variable.\n```python\n# my_script.py\nfrom serum import match, dependency, Context, inject\n\n@dependency\nclass BaseDependency:\n    def method(self):\n        raise NotImplementedError()\n\n\nclass ProductionDependency(BaseDependency):\n    def method(self):\n        print('Production!')\n\n\nclass TestDependency(BaseDependency):\n    def method(self):\n        print('Test!')\n\n\n@inject\ndef f(dependency: BaseDependency):\n    dependency.method()\n\n\ncontext = match(\n    environment_variable='MY_SCRIPT_ENV', \n    default=Context(ProductionDependency),\n    PROD=Context(ProductionDependency),\n    TEST=Context(TestDependency)\n)\n\nwith context:\n    f()\n```\n```\n\u003e python my_script.py\nProduction!\n```\n```\n\u003e MY_SCRIPT_ENV=PROD python my_script.py\nProduction!\n```\n```\n\u003e MY_SCRIPT_ENV=TEST python my_script.py\nTest!\n```\n## IPython Integration\nIt can be slightly annoying to import some `Context` and start it as a\ncontext manager in the beginning of every IPython session. \nMoreover, you quite often want to run an IPython REPL in a special context,\ne.g to provide configuration that is normally supplied through command line\narguments in some other way.\n\nTo this end `serum` can act as an IPython extension. To activate it,\nadd the following lines to your `ipython_config.py`:\n```python\nc.InteractiveShellApp.extensions = ['serum']\n```\nFinally, create a file named `ipython_context.py` in the root of your project. In it,\nassign the `Context` instance you would like automatically started to a global\nvariable named `context`:\n```python\n# ipython_context.py\nfrom serum import Context\n\n\ncontext = Context()\n```\nIPython will now enter this context automatically in the beginning of\nevery REPL session started in the root of your project.\n# Why?\nIf you've been researching Dependency Injection frameworks for python,\nyou've no doubt come across this opinion:\n\n\u003eYou dont need Dependency Injection in python. \n\u003eYou can just use duck typing and monkey patching!\n \nThe position behind this statement is often that you only need Dependency \nInjection in statically typed languages.\n\nIn truth, you don't really _need_ Dependency Injection in any language, \nstatically typed or otherwise. \nWhen building large applications that need to run in multiple environments however,\nDependency Injection can make your life a lot easier. In my experience,\nexcessive use of monkey patching for managing environments leads to a jumbled\nmess of implicit initialisation steps and `if value is None` type code.\n\nIn addition to being a framework, I've attempted to design `serum` to encourage\ndesigning classes that follow the Dependency Inversion Principle:\n\n\u003e one should “depend upon abstractions, _not_ concretions.\"\n\nThis is achieved by letting inheritance being the principle way of providing\ndependencies and allowing dependencies to be abstract.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuned%2Fserum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuned%2Fserum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuned%2Fserum/lists"}