Package: di-service-locator
di-service-locatorThedi-service-locatorlibrary provides an injection context using a service locator pattern to provide services throughout a codebase.  These services can be developed independantly as reusable, composable components that can depend on other features to provide richer components.The injection context can be driven from config meaning that the implementations for an app can be changed without having to actually change the code.There are numerous benefits to this approach including the ability to completely change a project from one cloud provider to another just by changing the config which will detail components such as bucket storage, instrumentation, execution etc.  It can also be used to provide simpler implementations for local execution, taking advantage of the file system and local processes, all without having to change any code.It is my hope that this approach can become a backbone to our projects and provide a more prescriptive way of developing libraries and solutions in a simple, composable way.  Making future development intuitive and efficient.Developing ServicesStart by abstracting what the service does, what people are going to need the service to do.  Keep the scope small and encapsulated.  Remember that services can be built on top of other services, so try to identify the smallest, reusable piecesCreate an interface (abstract base class with only abstract methods defined) for the service methodsWrite the implementation (or implementations).  The implementations can currently only be instantiated with primitive arguments, although we can improve that in the future.  But, for now, a litle care must be taken with what the implementations require to be instantiated.Write an example of the features config for inclusion in peoplesfeatures.jsonfilesMake everyone aware of your awesome, new service!Using ServicesOnce a service library has been installed into a project accessing the service is trivial.  The service can be obtained statically from a configured ServiceLocator instance by specifying the desired abstraction interface.  An appropriate implementation will be retrieved from the ServiceLocator cache or instantiated and returned if necessary.fromdi_service_locator.featuresimportServiceLocatorfromdi_service_locator.feature_defs.interfacesimportBlobStorageblob_storage=ServiceLocator.service(BlobStorage)key=blob_storage.put(data)Note that the code doesn't need to import or even know about the provided implementation.Configuring ServicesThe features injection context needs to be configured.The preferred mechanism for configuring the context is to use a json file specifying the available features and implementations.
An example of this json file;{"version":1,"features":{"blob_storage":{"factory":"di_service_locator.feature_defs.blob_storage.FileBlobStorage","implements":"di_service_locator.feature_defs.interfaces.BlobStorage","args":[],"kwargs":{"root_path":"/tmp/blobstorage"}}}}This file needs to be calledfeatures.jsonand should be located in the directory from which the application is started or the user home directory in a folder called.di.Additionally a special argument can be specified during the configuration of such services, thedefaultargument. This argument must be used if there are multiple definitions for the same factory and you want to force the Service Locator to return a specific  definition.Config propertiesIt is sometimes useful to be able to specify parameters to feature implementations at runtime.  The root path for a file blob storage implementation, for example.  These parameters may be user specific or even contain secrets that should not be checked in.To facilitate this need thefeatures.jsonfile supports property resolution on args and kwargs for a feature.  Properties can be indicated using a$prefix.{"version":1,"features":{"blob_storage":{"factory":"di_service_locator.feature_defs.blob_storage.FileBlobStorage","implements":"di_service_locator.feature_defs.interfaces.BlobStorage","args":[],"kwargs":{"root_path":"$STORAGE_ROOT_PATH"}}}}The property values can be specified via the command line, an environment variable or the project.envfile.  Properties will be located in order;command line overideenvironment variable.env fileTo specifiy on the command line prefix the property name with--;python-mmy_project.main--STORAGE_ROOT_PATH=/tmp/dsTo specify an environment variable;exportSTORAGE_ROOT_PATH=/tmp/ds
python-mmy_project.mainTo specify in a.envfile add the name and value to the file;STORAGE_ROOT_PATH=/tmp/dsConfiguring in codeThere is an option to configure the features context in code;Features.configure({"blob_storage":FactoryDefinition(fqn_impl_factory="di_service_locator.feature_defs.blob_storage.FileBlobStorage",fqn_interface="di_service_locator.feature_defs.interfaces.BlobStorage",args=[],kwargs={"root_path":"/tmp/blobstorage"},)})This configuration step needs to be performed before accessing any services and should probably be one of the first things done in an apps entry point.How to run locallyExecutemake start-dependenciesin order to start whichever dependencies your project might have. You should always have the base Python image.Executemake runand your application should be running.Executemake stop-dependencieswhen you no longer need to execute your project.Executemake sync-requirementsto rebuild the docker image.How to know whichmakerecipes are availableRunmakeHow to run testsRunmake testHow to run tests with coverage reportRunmake coverage