Package: db-able
db-ableFramework to implement basic CRUD operations with DB forDataObject.Quick startSetup in-lineSet up your connection string to your database.fromdb_ableimportclientclient.CONN_STR='{dialect}+{driver}://{username}:{password}@{host}:{port}/{database}?{query_args}'Environment VariableSet up connection string with an environment variable.exportDB_CONN_STR={dialect}+{driver}://{username}:{password}@{host}:{port}/{database}?{query_args}UsageImplement the mixins into your DataObject to inject CRUD methods.fromdo_pyimportRfromdb_ableimportCreatable,Deletable,Loadable,SavableclassMyObject(Creatable,Deletable,Loadable,Savable):db='{schema_name}'_restrictions={'id':R.INT,'key':R.INT}load_params=['id']create_params=['key']delete_params=['id']save_params=['id','key']my_obj=MyObject.create(key=555)my_obj=MyObject.load(id=my_obj.id)my_obj.key=777my_obj.save()my_obj.delete()Classmethodscreate,load, and methodssaveanddeleteare made available
to your DataObject class.Use provided SQL Generating utils to expedite implementation.fromdb_able.utils.sql_generatorimportprint_all_spsfromexamples.aimportAprint_all_sps(A)Examples"A" Python implementationfromdo_pyimportDataObject,Rfromdb_ableimportCreatable,Loadable,Savable,DeletableclassJson(DataObject):""" Nested Json object for A. """_restrictions={'x':R.INT,'y':R.INT}classA(Creatable,Loadable,Savable,Deletable):""" Basic DBAble implementation for unit tests. """db='testing'_restrictions={'id':R.INT,'string':R.NULL_STR,'json':R(Json,type(None)),'int':R.NULL_INT,'float':R.NULL_FLOAT,'datetime':R.NULL_DATETIME}load_params=['id']create_params=['string','json','int','float','datetime']save_params=['id','string','json','int','float','datetime']delete_params=['id']"A" MySQL Table structureCREATETABLEIFNOTEXISTS`testing`.`a`(`id`INTNOTNULLAUTO_INCREMENT,`string`VARCHAR(45)NULL,`json`JSONNULL,`int`INT(11)NULL,`float`FLOATNULL,`datetime`TIMESTAMPNULL,PRIMARYKEY(`id`));"A" MySQL CRUD Stored ProceduresUSE`testing`;DROPPROCEDUREIFEXISTS`testing`.`A_create`;DELIMITER$$CREATEDEFINER=`root`@`localhost`PROCEDURE`testing`.`A_create`(IN`_string`VARCHAR(45),IN`_json`JSON,IN`_int`INT,IN`_float`FLOAT,IN`_datetime`TIMESTAMP)BEGININSERTINTO`testing`.`a`(`string`,`json`,`int`,`float`,`datetime`)VALUES(`_string`,`_json`,`_int`,`_float`,`_datetime`);CALL`testing`.`A_load`(LAST_INSERT_ID());END;$$DELIMITER;USE`testing`;DROPPROCEDUREIFEXISTS`testing`.`A_delete`;DELIMITER$$CREATEDEFINER=`root`@`localhost`PROCEDURE`testing`.`A_delete`(IN`_id`INT)BEGINDELETEFROM`testing`.`a`WHERE`id`=`_id`;SELECTROW_COUNT()AS`deleted`;END;$$DELIMITER;USE`testing`;DROPPROCEDUREIFEXISTS`testing`.`A_load`;DELIMITER$$CREATEDEFINER=`root`@`localhost`PROCEDURE`testing`.`A_load`(IN`_id`INT)BEGINSELECT*FROM`testing`.`a`WHERE`id`=`_id`;END;$$DELIMITER;USE`testing`;DROPPROCEDUREIFEXISTS`testing`.`A_save`;DELIMITER$$CREATEDEFINER=`root`@`localhost`PROCEDURE`testing`.`A_save`(IN`_id`INT,IN`_string`VARCHAR(45),IN`_json`JSON,IN`_int`INT,IN`_float`FLOAT,IN`_datetime`TIMESTAMP)BEGINUPDATE`testing`.`a`SET`string`=`_string`,`json`=`_json`,`int`=`_int`,`float`=`_float`,`datetime`=`_datetime`WHERE`id`=`_id`;CALL`testing`.`A_load`(`_id`);END;$$DELIMITER;Advanced Use CasesUserThis implementation requires extension of core functionality
to support salting, hashing, and standard password security practices.importcryptimporthashlibfromdo_pyimportRfromdb_ableimportLoadable,Creatable,Savable,DeletableclassUser(Loadable,Creatable,Savable,Deletable):"""User DataObject with DB CRUD implementation.Customized to handle password encryption and security standards."""db='testing'_restrictions={'user_id':R.INT,'username':R.STR,'salt':R.STR,'hash':R.STR}_extra_restrictions={'password':R.STR,}load_params=['user_id']create_params=['username','salt','hash']# password is required. salt and hash are generated.save_params=['user_id','username','salt','hash']delete_params=['user_id']@classmethoddefgenerate_salt(cls):""":rtype: str"""returncrypt.mksalt(crypt.METHOD_SHA512)@classmethoddefgenerate_hash(cls,password,salt):""":type password: str:type salt: str:rtype: str"""salted_password=password+saltreturnhashlib.sha512(salted_password.encode()).hexdigest()@classmethoddefcreate(cls,password=None,**kwargs):"""Overloaded to prevent handling raw password in DB.:type password: str:keyword username: str:rtype: User"""password=cls.kwargs_validator('password',password=password)[0][1]salt=cls.generate_salt()kwargs.update({'salt':salt,'hash':cls.generate_hash(password,salt)})returnsuper(User,cls).create(**kwargs)defsave(self,password=None):"""Overloaded to support updating password with security.:type password: str:rtype: bool"""ifpassword:password=self.kwargs_validator('password',password=password)[0][1]self.salt=self.generate_salt()self.hash=self.generate_hash(password,self.salt)returnsuper(User,self).save()User MySQL Table StructureCREATETABLEIFNOTEXISTS`user`(`user_id`intNOTNULLAUTO_INCREMENT,`username`varchar(255)NOTNULL,`salt`varchar(255)NOTNULL,`hash`varchar(255)NOTNULL,PRIMARYKEY(`user_id`));User MySQL CRUD Stored ProceduresUSE`testing`;DROPPROCEDUREIFEXISTS`testing`.`User_load`;DELIMITER$$CREATEDEFINER=`root`@`localhost`PROCEDURE`testing`.`User_load`(IN`_user_id`VARCHAR(255))BEGINSELECT*FROM`testing`.`user`WHERE`user_id`=`_user_id`;END;$$DELIMITER;USE`testing`;DROPPROCEDUREIFEXISTS`testing`.`User_create`;DELIMITER$$CREATEDEFINER=`root`@`localhost`PROCEDURE`testing`.`User_create`(IN`_username`VARCHAR(255),IN`_salt`VARCHAR(255),IN`_hash`VARCHAR(255))BEGININSERTINTO`testing`.`user`(`username`,`salt`,`hash`)VALUES(`_username`,`_salt`,`_hash`);CALL`testing`.`User_load`(LAST_INSERT_ID());END;$$DELIMITER;USE`testing`;DROPPROCEDUREIFEXISTS`testing`.`User_save`;DELIMITER$$CREATEDEFINER=`root`@`localhost`PROCEDURE`testing`.`User_save`(IN`_user_id`VARCHAR(255),IN`_username`VARCHAR(255),IN`_salt`VARCHAR(255),IN`_hash`VARCHAR(255))BEGINUPDATE`testing`.`user`SET`username`=`_username`,`salt`=`_salt`,`hash`=`_hash`WHERE`user_id`=`_user_id`;CALL`testing`.`User_load`(`_user_id`);END;$$DELIMITER;USE`testing`;DROPPROCEDUREIFEXISTS`testing`.`User_delete`;DELIMITER$$CREATEDEFINER=`root`@`localhost`PROCEDURE`testing`.`User_delete`(IN`_user_id`VARCHAR(255))BEGINDELETEFROM`testing`.`user`WHERE`user_id`=`_user_id`;SELECTROW_COUNT()AS`deleted`;END;$$DELIMITER;Best PracticesIt is recommended to store your SQL files within your code repository for ease of reference. Refer todo-able/tests/sqlfor an example of code organization.Generally, explicitly defining the columns for your %s_load stored procedures is better for forward compatibility as
changes are implemented in the long run.Testing & Code QualityCode coverage reports for master, branches, and PRs
are postedhere in CodeCov.