Package: dinao
IntroductionWhat is DINAO? Well it might be easier to tell you what it’s not.  DINAO Is Not
An ORM.  If you want an ORM,SQLAlchemyis absolutely the best python has to
offer.Target AudienceDo you like writing SQL? Do you hate all the boiler plate involved with setting
up connections and cursors then cleaning them up?  Would you just like
something simple that executes a query and can map the results to simple data
classes?  Then DINAO is for you!Influences and Guiding PrinciplesThe APIs implemented mirror libraries I’ve used in other ecosystems.
Specifically, you may notice similarities to the JDBI Declarative API or the
MyBatis interface mappers.  This is because I very muchlikethis approach.
You’re the developer, I’m just here to reduce the number of lines of code you
have to write to meet your goal.  At the end of the day you know your schema
and database better than I do, and so you know what kinds of queries you need
to write better than I do.How do you pronounce DINAO?You pronounce it “Dino” like “Dinosaur”.  Going back to plain old SQL probably
seems rather archaic after all.UsageInstall via pip:$ pip install dinaoYou will also need to install your backend driver.  Backends + drivers
supported are:Sqlite3 via Python’s standard libraryPostgreSQL via psycopg2Basic ExampleDINAO focuses binding functions to scoped connections / transactions against
the database and using function signatures and type hinting to infer mapping
and query parameterization.Below shows a simple example of DINAO usage. For more comprehensive usage and
feature showcase seeexamples.fromtypingimportListfromdataclassesimportdataclassfromdinao.backendimportcreate_connection_pool,Connectionfromdinao.bindingimportFunctionBinderbinder=FunctionBinder()@dataclassclassMyModel:name:strvalue:int@binder.execute("CREATE TABLE IF NOT EXISTS my_table ( ""  name VARCHAR(32) PRIMARY KEY, ""  value INTEGER DEFAULT 0"")")defmake_table():pass@binder.execute("INSERT INTO my_table (name, value) VALUES(#{model.name}, #{model.value}) ""ON CONFLICT (name) DO UPDATE ""  SET value = #{model.value}""WHERE my_table.name = #{model.name}")defupsert(model:MyModel)->int:pass# This is an example of a query where a template variable is directly# replaced in a template.  This is via a template argument denoted with# !{column_name}.  The #{search_term} on the other hand uses proper# escaping and parameterization in the underlying SQL engine.## IMPORTANT: This is a vector for SQL Injection, do not use direct template#            replacement on untrusted inputs, especially those coming from#            users.  Ensure that you validate, restrict, or otherwise limit#            the values that can be used in direct template replacement.#@binder.query("SELECT name, value FROM my_table WHERE !{column_name}LIKE #{search_term}")defsearch(column_name:str,search_term:str)->List[MyModel]:pass@binder.transaction()defpopulate(cnx:Connection=None):make_table()cnx.commit()upsert(MyModel("testing",52))upsert(MyModel("test",39))upsert(MyModel("other_thing",20))if__name__=='__main__':con_url="sqlite3:///tmp/example.db"db_pool=create_connection_pool(con_url)binder.pool=db_poolpopulate()formodelinsearch("name","test%"):print(f"{model.name}:{model.value}")ContributingCheck out ourcode of conductandcontributing documentation.Release ProcessThis library adheres toosemantic versioning 2.0.0standards, in general
that means, given a version number MAJOR.MINOR.PATCH, increment:MAJOR version when you make incompatible API changesMINOR version when you add functionality in a backwards compatible mannerPATCH version when you make backwards compatible bug fixesChanges for the next version should be accumulated on the main branch until
such time that there is enough confidence in the build that it can be released.
When this is done, a repository administrator opens a PR to bump the version in__version__.pyupdates the change logs, merges this PR then tags the merge
with the release version.  Only tagged commits of main are built and published.