Package: pynoorm
PyNoORM consists of several very loosely-coupled classes that facilitate the use of Python in a web or SQL
context without having to rely on an ORM.  Working with an ORM is entirely possible, in fact, it’s used with
the Django ORM and SQLAlchemy in an application that interfaces with Oracle, Microsoft SQL Server and PostgreSQL all at the same time.Focus is on:simplicity for the usersupport for databases that are not under “controlled” by the Python application or may be read-only for it.performanceClassRoleBinderabstract SQL query bindingLinkerjoin objects togetherTemplateGenerator(to be added)generate Django Templates dynamically from query results, loosely
inspired by Django Tables 2The Binder classA Binder support easier raw SQL by abstracting differences in the underlying database’s bind variable syntax and also substituting bind variables from a list of arguments, using dict, then attribute lookup.Using native database binds also helps to protect you against SQL injection attacks.supported:  PostgreSQL, sqlite3, Oracle, MySQL, SQL ServerBasic UseSimplesqlite3example:from pynoorm.binder import Binder
binder = Binder.factory("qmark")

query, parameters = binder("select * from orders where custid = %(custid)s", dict(custid="ACME"), binder)queryandparametersare now in the sqlite3/qmark format:>>> print(query)
select * from orders where custid = ?
>>> print(parameters)
('ACME',)Oracle, with multiple parametersimport cx_Oracle
binder_ora = Binder.factory(cx_Oracle.paramstyle)

#just for test... assign a custid for attribute lookup
binder_ora.custid = "AMAZON"

tqry = "select * from orders where custid = %(custid)s and has_shipped = %(shipped)s"
query, parameters = binder_ora(tqry, binder_ora, dict(custid="ACME", shipped=1))

>>> print(query)
select * from orders where custid = :custid and has_shipped = :shipped
>>> print(parameters)
{'shipped': 1, 'custid': 'AMAZON'}SQL IN list criteria:This allows binding of Python lists as standard SQLin('foo','bar')expressions, but as a prepared statement.It relies on using‘l’, rather than‘s’as the format qualifier.  Notice the%(custid)lbelowfrom pynoorm.binder import Binder
binder = Binder.factory("qmark")

query, parameters = binder(
    "select * from orders where custid in (%(custid)l)"
    , dict(custid=["ACME","FOO","BAR"])
    )Contents ofqueryandparameters:select * from orders where custid in (?, ?, ?)
('ACME', 'FOO', 'BAR')And now with an empty list:query, parameters = binder(
    """select *
    from orders
    where custid in (%(custid)l)
    and status=%(status)s"""
    , dict(custid=[], status="any")
    )Contents ofqueryandparameters:select * from orders where custid in (NULL) and status=?
('any',)Featuresadjust query to support database parameter stylefind and prepare bind parameters from*args.The Linker classA Linker allows you to join objects or dictionaries without the need for an ORM.  You can think of it as performingparent-childlinking, but it usesleft-rightinstead as a more neutral terminology instead.Basic useSample data, in dictionaries:customers = [
    dict(id=1, xref=1),
    dict(id=2, xref=2),
]

orders = [
    dict(custid=1, xref=1, orderid=11),
    dict(custid=1, xref=1, orderid=12),
    dict(custid=2, xref=2, orderid=21),
    dict(custid=2, xref=2, orderid=22),
]Create a linker, then a lookup dictionary for the left side.  Finally, link the left and right side.linker = Linker(key_left="id")
lookup = linker.dict_from_list(customers)
linker.link(lookup, orders, attrname_on_left="orders", key_right="custid")The customers now have anorderslist:[ { 'id': 1,
    'orders': [ { 'custid': 1, 'orderid': 11, 'xref': 1},
                { 'custid': 1, 'orderid': 12, 'xref': 1}],
    'xref': 1},
  { 'id': 2,
    'orders': [ { 'custid': 2, 'orderid': 21, 'xref': 2},
                { 'custid': 2, 'orderid': 22, 'xref': 2}],
    'xref': 2}]Featuressupports objects or dictionariestakes basic Python objects so can join across different databases, allowing for example tagging of objects in a read-only databaseallows compound field keys and aliasingorphans, on the left or the right, can be initialized with empty attribute values.Note on Python 3.7 support:3.7 tests run to success locally, but travis-ci does not support Python 3.7 yet.  So expectbuildsto show
“failing” 3.7, pending resolution ofTravisissue485.CreditsThis package was created withCookiecutterand theaudreyr/cookiecutter-pypackageproject template.History0.1.0 (2016-02-17)First release on github.0.1.1 (2016-02-22)Registered on PyPI0.2.0 (2016-04-12)Added support for Python 3.3+ and MySQL0.3.0 (2017-09-06)Added SQL Server supportAdded Linker class to support object cross-referencing0.4.0 (2018-07-24)Updating to Beta statusOptimized Linker classPython list => SQL IN (xxx, yyy) functionality on Binder.0.4.1 (2018-08-07)adjusted for Python 30.4.2 (2019-01-10)ran Black for code formattingupdated PyYaml to 4.2b4 to fix security vulnerability0.4.3 (2019-01-10)removed Python 3.7 from tox since that Python version is not supported yet by tox.0.4.4 (2019-01-15)adjusted list binding variable names from__xxx_000toxxx_000__because leading underscore are invalid under Oracle.1.0.2 (2019-07-15)Docs:  added the D3.js Tree Linker example.Code is stable and hasn’t needed fixes or API changes despite significant use cases.