Package: siringa
Aboutsiringa(meaningsyringein Italian) is a minimalist, idiomaticdependency injectionandinversion of controllibrary
forPython, implemented inHy, a homoiconic Lisp dialect for Python.To get started, take a look to thedocumentation,API,tutorialandexamples.FeaturesSimple, idiomatic and versatileprogrammatic API.Annotation based dependency injection that isPEP 3017andPEP 0484friendly.First-class decorator driven dependency injection and registering.Ability to create multiple dependency containers.Hierarchical dependency containers based on inheritance.Dependency inference based on pattern-matching techniques.First-class support for dependency mocking for better testing.Detects cyclic dependencies (work in progress).Small and (almost) dependency-free library.Works with CPython 3+.Design philosophyCode instrumentation should be non-intrusive and idiomatic.Explicitness over implicitness: dependencies and injections much be explicitly defined.Python idiomatic: embrace decorators and type annotations.Minimalism: less enables more.Uniformity: there is only one way to declare and consume dependencies.Predictability: developer intentions must persist based on explicitly defined intention.Domain agnostic: do not enforce any domain-specific pattern.InstallationUsingpippackage manager:pipinstall--upgradesiringaOr install the latest sources from Github:pipinstall-egit+git://github.com/h2non/siringa.git#egg=siringaTutorialImporting siringaimportsiringaInstrumenting dependenciessiringaembraces type hints/arguments annotation Python syntax for
dependency inference and pattern matching.@siringa.injectdeftask(x,y,logger:'!Logger'):logger.info('task called with arguments:{},{}'.format(x,y))returnx*yYou can optionally annotate dependencies viasiringatype annotations:fromsiringaimportA@siringa.injectdeftask(x,y,logger:A('Logger')):logger.info('task called with arguments:{},{}'.format(x,y))returnx*yFinally, for a DRYer approach you can simply annotate dependencies with!annotation flag.In this case, the argument name expression will be used for dependency inference.fromsiringaimportA@siringa.injectdeftask(x,y,Logger:'!'):Logger.info('task called with arguments:{},{}'.format(x,y))returnx*yRegistering dependenciessiringaallows you to rely on decorators for idiomatic dependencies registering.Dependency name is dynamically inferred at registration time based onclassorfunctionname.@siringa.registerclassLogger(object):logger=logging.getLogger('siringa')@staticmethoddefinfo(msg,*args,**kw):logger.info(msg,*args,**kw)However, you can define a custom dependency name by simply passing astringas first argument:@siringa.register('MyCustomLogger')classLogger(object):...Finally, you can register dependencies with a traditional function call, such as:classLogger(object):passsiringa.register('MyCustomLogger',Logger)classcompute(x,y):returnx*ysiringa.register('multiply',compute)Invocationsiringawraps callable object in the transparent and frictionless way abstracting things for developers.You can invoke or instantiate any dependency injection instrumented object
as you do traditionally in raw Python code andsiringawill do the rest for you inferring and pattern-matching
required dependencies accordingly for you.Below is an example of how simple it is:# Call our previously declared function in this tutorial.# Here, siringa will transparently inject required dependencies accordingly,# respecting the invokation arguments and order.task(2,2)# => 4Let’s demostrate this with a featured example:importsiringa@siringa.registerdefmul(x,y):returnx*y@siringa.registerdefmul2(x,mul:'!mul'):returnmul(x,2)@siringa.registerdefpow2(x):returnx**2@siringa.injectdefcompute(x,pow:'!pow2',mul:'!mul2'):returnpow(mul(x))compute(2)# => 16You can also use the invocation API in case that the target object
was not properly instrumented as dependency:@siringa.registerdefmul2(x):returnx*2# Note that the function was not instrumented yet!defcompute(x,mul:'!mul2'):returnmul(x)siringa.invoke(compute,2)Create a new dependency containersiringaprovides a built-in global dependency container for usability purposes,
but you can create as much containers as you want.In thesiringaidioms, this means creating a new dependency layer which provides its
own container and dependency injection API, pretty much as the global package API.You can create a new dependencieslayersuch as:layer=siringa.Layer('app')# Then you can use the standard APIlayer.register('print',print)# Then you can use the standard API@layer.injectdefmul2(x,print:'!'):print('Argument:',x)returnx*2mul2(x)A dependency layer can inherit from a parent dependency layer.This is particularly useful in order to create a hierarchy of dependency layers
where you can consume and inject dependencies from a parent container.parent=siringa.Layer('parent')child=siringa.Layer('child',parent)# Register a sample dependency within parent@parent.registerdefmul2(x):returnx*2# Verify that the dependency is injectable from child layerparent.is_injectable('mul2')# Truechild.is_injectable('mul2')# True@child.injectdefcompute(x,mul:'!mul2'):returnmul(x)compute(2)# => 2Mocking dependenciessiringaallows you to define mocks for dependencies, which is particularly useful during testing:@siringa.registerclassDB(object):defquery(self,sql):return['john','mike']@siringa.mock('DB')classDBMock(object):defquery(self,sql):return['foo','bar']@siringa.injectdefrun(sql,db:'!DB'):returndb().query(sql)# Test mock callassertrun('SELECT name FROM foo')==['foo','bar']# Once done, clear all the mockssiringa.unregister_mock('DB')# Or alternatively clear all the registed mocks within the containersiringa.clear_mocks()# Test read callassertrun('SELECT name FROM foo')==['john','mike']Historyv0.1.3 / 2017-04-25fix(invoke): missing export symbol at public levelrefactor(docs): update disclaimer notefix(docs): remove unused tutorial.rst filerefactor(Makefile): use –commit when bumping versionv0.1.2 / 2017-04-25feat(examples): add inject flag examplefix(injector): process inject flag “!” based annotations accordinglyfix(register): return decorated injectable callable object, if neededfix(register): return decorated injectable callable object, if neededrefactor(analyzer): use method access notationrefactor(docs): typo in design philosophy sectionfix(docs): typo in rst syntaxfix(examples): type on mocking example commentv0.1.1 / 2017-04-24fix(core): handle not present __init__ member in classes. feat(examples): add mocking examplefix(docs): type in about sectionfix(setup.py): package descriptionv0.1.0 (2017-04-23)First version (beta).