Package: conject
ConjectThis library automates DI - allowing to create dependencies by its declarative description.
Features:Creates application components hierarchy automatically.Eliminates the need to implemenent config files support - allows you to configure any application component out of
the box, select implementations and link them together.Makes it easy to finalize components.Supports synchronous and asynchronous interfaces.Doesn't require components code modification (assuming they are written in DI-compatible style).ExampleLet's assume we have a simple http service:classHttpClient(abc.ABC):@abc.abstractmethoddefrequest(self,method:str,url:str):raiseNotImplementedErrorclassMockHttpClient(HttpClient):defrequest(self,method:str,url:str):...# return fake responseclassPooledHttpClient(HttpClient):def__init__(self,proxy:str,user_agent:str):...defrequest(self,method:str,url:str):...def__enter__(self):returnselfdef__exit__(self,exc_type,exc_val,exc_tb):...# cleanupclassFbApiClient:def__init__(self,http_client:HttpClient,access_token:str,api_ver:str):...defget_user_info(self,user_id:str)->dict:...# use HttpClient to fetch user infoclassFetchFbUserHandler:def__init__(self,fb_api_client:FbApiClient):...def__call__(self,req):...# use FbApiClient.get_user_info and returndefcreate_http_app(fetch_fb_user_handler:FetchFbUserHandler):http_app=HttpApp([('GET','/fetch_fb_user/',fetch_fb_user_handler),])returnhttp_appIn order to fully construct our HttpApp we need to:Add config option to choose HttpClient implementation.UseExitStacksincePooledHttpClientis a context manager (whileMockHttpClientisn't).Add config options forproxy,user_agent,access_token,api_verparameters.conjectlets you avoid doing it manually.fromconjectimportDepSpec,Impldefrun(config:dict)->None:spec=DepSpec([Impl(Impl.Func,'http_app',create_http_app),Impl(Impl.CtxMgr,'pooled_http_client',PooledHttpClient),Impl(Impl.Class,'mock_http_client',MockHttpClient),Impl(Impl.Class,'fb_api_client',FbApiClient),Impl(Impl.Class,'fetch_fb_user_handler',FetchFbUserHandler),])withspec.start_container(config)ascontainer:# get or create component with name 'http_app'#  expected type is optional, but allows type-checkinghttp_app=container.get('http_app',HttpApp)...# run_http_app(http_app)Now we are able to control our components via configuration (formatted as toml for example):[http_client]# component name, can be anything-impl='pooled_http_client'# use implementation named 'pooled_http_client'user_agent='super-app/10'# implementation params[fb_api_client]access_token='abcdefgh'api_ver='7.0'Another possible config:[proxied_http_client]-impl='pooled_http_client'proxy='my-squid:3128'[proxied_fb_api_client]-impl='fb_api_client'http_client={-ref='proxied_http_client'}# use component named 'proxied_http_client'access_token='fake_token'api_ver='7.0'[http_app]fb_api_client={-ref='proxied_fb_api_client'}conjectallows some configuration parts to be omitted:If your component name matches implementation name component's-implproperty can be omitted.You can omit implementation parameter. It will receive:default value, if there is one in the factory signature;component with the same name;If there is no need to set any of component's properties you can omit it completely.Registering implementationsFactory types:FactoryType.Value- just value.FactoryType.Func- function returning implementation.FactoryType.Class- implementation-class.FactoryType.GenFunc- function generating implementation that can finalize it afterwards.FactoryType.CtxMgr- implementation context manager.FactoryType.AFunc- likeFunc, but async.FactoryType.AGenFunc- likeGenFunc, but async.FactoryType.ACtxMgr- likeCtxMgr, but async.Factory can receive any parameters except for variadic (*args,**kwargs) andpositional-only.To register your implementations you need to createDepSpec(orAsyncDepSpec) object.Registration ways:fromconjectimportDepSpec,Implspec=DepSpec()# you can pass impls to __init__spec.add(Impl.CtxMgr,'some_cls',SomeCls)spec.add_many([Impl(Impl.GenFunc,'some_gen_func',some_gen_func),])@spec.decorate(Impl.Func)defsome_func(some_cls:SomeCls):return1Creating a containerTo create a container you need to enter contextspec.start_container(config: Any), whereconfigwill be used to
configure components.Example:defrun(spec:DepSpec)->None:config={'auth_provider':{'-impl':'google_auth_provider'}}withspec.start_container(config)ascontainer:auth_provider=container.get('auth_provider')assertisinstance(auth_provider,GoogleAuthProvider)Using a containerDependency container lazily creates components and manages their lifetime.def conject.Container.get(component_name: str) -> Anyasync def conject.AsyncContainer.get(component_name: str) -> AnyYou can fetch any component by callingget. It will create every component dependency during this call.These created objects will be finalized afterspec.start_container()context exit only. The order of finalization will
be the reversed order of their creation.Usually you will need only one container that runs for a whole program lifetime.Components configurationConfig should be a dict in which every key describes one component. Value is also a dict with options:-implis a special option, it allows you to select implementation name for this component. It could be omitted.All other options represents factory parameters. Besides simple values, you can use special ones:{'-ref': '<component_name>'}Pass another component by its name.{'-expr': '<expression>'}Pass result of evaluation of python-expression.The expression may depend on other components fromrefsnamespace, e.g.refs.other_component.Example:importosspec.add(Impl.Value,'environ',os.environ)spec.add(Impl.Class,'fb_api_client',FbApiClient)Config (formatted as toml):[fb_api_client]access_token={-expr='refs.environ["FB_API_ACCESS_TOKEN"]'}Special options-ref/-exprcan also be used inside lists and dicts (including nested).Type checkingIn case your factories are type-annotated,conjectwill check its parameters in runtime. This is done viapydanticlibrary.Also this allows you to pass e.g.datetimeparam from your toml configuration. To get the list of supported types seepydantic docs.Caveat: your mutable parameter values (including dict/list/dataclass instances) will be rebuilt from scratch, so you
may not receive the same instance you've expected.Utilsdef conject.utils.load_package_recursively(package_name: str) -> NoneRecursively import all package's modules. Doesn't handle namespace packages for now.def conject.utils.skip_type_check(value: Any) -> SkipTypeCheckAsk conject to assume this value is  correct as any param type.