Package: quickd
quickdDecorator type-based dependency injection for Python📦 InstallationThe packagequickdsupports Python >= 3.5. You can install it by doing:$pipinstallquickd📜 ExampleHere is a quick example:fromquickdimportinject,factoryclassDatabase:passclassPostgreSQL(Database):def__str__(self):return'PostgreSQL'classMySQL(Database):def__str__(self):return'MySQL'@injectdefprint_database(database:Database):returnprint(database)@factorydefchoose_database()->Database:returnPostgreSQL()print_database()# Prints: PostgreSQLprint_database(MySQL())# Prints: MySQL🚀 UsageThere are only 3 decorators that compose the whole framework@factoryRegisters an instance for a specific type for later use with@injectIs mandatory to annotate the function with the return type of the class that you want to inject laterIt isnotdynamic, so the implementation can only be chosen oncefromquickdimportfactory@factorydefchoose_database()->Database:returnPostgreSQL()@injectInjects dependencies to a function by matching its arguments types with what has been registeredAs you can see below, it also works with constructorsfromquickdimportinject@injectdefprint_database(database:Database):returnprint(database)classUserService:@injectdef__init__(self,database:Database):pass@serviceRegisters a class to be later injectable without using@factoryIt also applies@injectto its constructorfromquickdimportservice,inject@serviceclassUserService:def__init__(self):self.users=['Bob','Tom']defall(self):returnself.usersdefadd(self,user):self.users.append(user)@injectdefget_users(service:UserService):returnservice.all()@injectdefadd_user(service:UserService):returnservice.add("Pol")get_users()# ['Bob', 'Tom']add_user()get_users()# ['Bob', 'Tom', 'Pol']👨‍🍳 RecipesHere are some common solutions to scenarios you will face.Interfacesfromabcimportabstractmethodfromquickdimportinject,factoryclassUserRepository:@abstractmethoddefsave(self,user):pass@abstractmethoddefsearch(self,id):passclassUserCreator:@injectdef__int__(self,repository:UserRepository):self.repository=repositorydefcreate(self,user):self.repository.save(user)classMySQLUserRepository(UserRepository):def__int__(self,host,password):self.sql=MySQLConnection(host,password)defsave(self,user):self.sql.execute('INSERT ...')defsearch(self,id):self.sql.execute('SELECT ...')@factorydefchoose_user_repository()->UserRepository:# Notice super class is being usedreturnMySQLUserRepository('user','123')TestingFollowing the above example we can create a unit test mocking the persistance, which will make our tests easier and
faster.fake_user={'id':1,'name':'Tom'}classFakeUserRepository(UserRepository):defsave(self,user):assertuser==fake_userrepository=FakeUserRepository()user_creator=UserCreator(repository)user_creator.create(fake_user)ConfigurationThere are multiple ways to configure your classes. A simple approach is to use environment variables on your factory
annotated methods.importosfromquickdimportfactory@factorydefchoose_database()->Database:username=os.environ.get("POSTGRES_USER")password=os.environ.get("POSTGRES_PASS")returnPostgreSQL(username,password)🧠 MotivationDependency injection provides a great way to decouple your classes in order to improve testability and maintainability.Frameworks likeSpringorSymfonyare loved by the community.I will just add a parameter to the constructor and Spring will fill with a global instance of the classThese frameworks rely heavy on the type system, to know which class should go where.From Python 3.5 we have thetypingpackage. This addition allows us to
have the dependency injection framework that Python deserves.