Package: django-sqltemplate
django-sqltemplateDatabase query tool for Django, based on SQL templatesDevelopment statusAlphaAPI may be radically changed until first Beta releaseNot tested automatically yetRoadmap0.6 - stable API0.7 - automated tests, first Beta release0.8,<1.0 - minor improvements without API changes, bugfixes1.0 - first Stable releaseIntroductionSQL is a great and poweruful DSL, which is easeier in maintenance
when you're working on complext queries (i.e. reporting queries).
But the main problem of raw SQL is a commonly used "spaghetti" anti-pattern,
when you're embedding/building SQLs directly in your code.The solution comes from templating SQLs idea anddjango-sqltemplateis a simple implementation of it.QuickstartInstall the packagepip install django-sqltemplateAdd application to the INSTALLED_APPSINSTALLED_APPS = [
       ...
       'djsqltemplate',
       ...
       ]Add SQL template(s)Makesqltemplatesdirectory in your Django app (your app must be added toINSTALLED_APPS):mkdir <django-project>/<my-app>/sqltemplatesPuthello.sqltemplate insqltemplatesdirectory:hello.sql (assuming sqlite syntax)select'Hello '||:nameasmessageQuery the database>>>importdjsqltemplate>>>hello=djsqltemplate.get('hello.sql')>>>printhello.values(name='Marcin')[{'message':u'Hello Marcin'}]If query returns just one row (as in example above) you may read result
directly using.scalar()method:>>>printhello.scalar(name='Marcin')HelloMarcinTo fetch results as a list of dictionaries use.values()method:>>>printhello.values(name='Marcin')[{'message':u'Hello Marcin'}]To fetch results as a list of tuples use.values_list()method:>>>printhello.values_list(name='Marcin')[(u'Hello Marcin',)]To fetch results as iterator over tuples use.iterator()method:>>>printhello.iterator(name='Marcin')<generatorobject_fetchat0x7f8abd202870>To fetch results as iterator over dictionaries use.dictiterator()method:>>>printhello.dictiterator(name='Marcin')<generatorobject_fetchat0x7f8abd202820>Advanced examplesThe CounterLet's assume that we want to count rows returning fromhello.sqlquery.
To do that we should create a sql for the counter. But instead of making
a new file, we'll create it from string, to show how.from_string()works:>>>count=djsqltemplate.from_string('select count(*) from ({{ sql|safe }}) x')Then join the queries together:>>>importdjsqltemplate>>>hello=djsqltemplate.get('hello.sql').bind(name='Marcin')>>>count=djsqltemplate.from_string('select count(*) from ({{ sql|safe }}) x')>>>printcount.scalar(sql=hello)1As you can see the:namevariable was replaced withMarcinstring,
and thesqltemplate variable (from The Counter query) was replaced
byhello.sqlsubquery.How it looks?>>>printcount.bind(sql=hello).pretty()SELECTcount(*)FROM(SELECT'Hello '||:nameASmessage)xHow it works?countandhelloobjects areTemplateQueryinstances:>>>count,hello(<sqltemplate.query.TemplateQueryat0x7f8abd1ee610>,<sqltemplate.query.TemplateQueryat0x7f8abd1ee210>)TheTemplateQuerywraps DjangoTemplateinstance together with specified
context.  CallingTemplateQueryproduces new instance with extended context
(internally using.bind()method), and the outermost context is extended by
context of embedded templates.Context may be set at the factory time settingcontextargument or by
implicit call of.bind()method. Also you can pass extra context arguments
directly to.values(),.values_list(),.iterator(),.dictiterator()and.scalar().>>>hello=djsqltemplate.get('hello.sql',context={'name':'Marcin'})>>>printhello.context{'name':'Marcin'}>>>hello=djsqltemplate.get('hello.sql')>>>printhello.context{}>>>hello_marcin=hello.bind(name='Marcin')>>>printhello_marcin.context{'name':'Marcin'}>>>printhello.scalar(name='Marcin')HelloMarcinSo in the Counter example we're settinghelloinstance as asqlvariable
for thecounter.sqltemplate, which is resolved and rendered by{{ sql|safe }}expression, and then (at the execution time) thenamevariable is passed tocursor.execute()(which is safe and the preferred way
of passing query parameters).Remeber that preparing templates with additional context makes a new instance
(a copy) of the original object. This will allow you for easy query
customization dependend of your requirements.Countries searcherLet's prepare a test table (still assuming sqlite as a db engine):echo"create table countries (id int, name varchar(64));"|sqlite3db.sqlite3Fill the example data:echo"insert into countries (id, name) values (1, 'Poland'), (2, 'Slovakia'), (3, 'Czech Republic');"|sqlite3db.sqlite3Addcountries.sqlquery template:selectid,namefromcountries{%ifsearch_for%}wherenamelike'%'||:search_for||'%'{%endif%}{%iflimit%}limit:limit{%endif%}Instantiatecountandcountriestemplates:>>>count=djsqltemplate.get('counter.sql')>>>countries=djsqltemplate.get('countries.sql')Ask for countries containg letter "a" in their names:>>>printcountries.values(search_for='a')[{'id':1,'name':u'Poland'},{'id':2,'name':u'Slovakia'}]then count the results:>>>printcount.scalar(sql=countries.bind(search_for='a'))2and limit results if you want:>>>printcountries.values(search_for='a',limit=1)[{'id':1,'name':u'Poland'}]Simple?Multiple database connectionsTemplateQueryclass provides.using()method which allow you to
change connection used to querying database. Just provide connection
name (alias) same as for Django'sQuerySet.>>>printcountries.using('default').values()You can set connection name at factory time:>>>countries=djsqltemplate.get('countries.sql',using='default')And you can usedjsqltemplate.using()as a context manager:withdjsqltemplate.using('default')astpl:countries=tpl.get('countries.sql')printcountries.values()Please note thattplvariable is a new factory instance, which will
automatically set proper connection to all createdTemplateQueryobjects. Direct call todjsqltemplate.get()will create objects same
as before, without connection set, because it is a shortcut for default
factory method.Default contextSometimes you may need to set some defaults. To do that you can set
default context at a factory time:>>>countries=djsqltemplate.get('countries.sql',context={'limit':2})And by usingdjsqltemplate.context()context manager:withdjsqltemplate.context(limit=1)astpl:countries=tpl.get('countries.sql')printcountries.values()Setting default context and connection togetherIf you want to set default context together with specific connection,
usedjsqltemplate.scope()context manager:withdjsqltemplate.scope(context={'limit':2},using='default')astpl:countries=tpl.get('countries.sql')printcountries.values()Motivationdjango-sqltemplateis designed for managing queries of mid/large complexity
(like queries above 100 SLOCs, incl. window functions, non-generic syntax,
etc)Maintenance of a complex queries is way faster using raw SQL instead of ORM
objects (Q(),F(),.aggregate(), etc)The querying should be simplest as possible, incl. joining / embedding
templates (we don't want to handle cursors and connections instances manually)It is not a replacement for Django ORM nor SQLAlchemy, and may be used
together with (i.e.sqlalchemy.text(str(countries(search_for='a')))or Django'sManager.raw())There are many good template engines (Django Templates, Jinja2), so we just
need to use them and not reinvent the wheelDjango 1.8+ has support for multiple templating enginesDjango is a most popoular RAD framework for Python, but with limited ORMRequirementsDjango 1.8+Dependencies:sqltemplate >= 0.5.0LicenseBSD