Records: Python library for making raw SQL queries to Postgres databases
github.com
github.com
https://github.com/cjauvin/little_pger
I have been using this for myself for quite some time, but feedback would be very appreciated!
Edit: A cool thing about it (I believe) is that it provides an "upsert wrapper" which makes use of either the new PG 9.5 "on conclict" mechanism if it's available, or an "update if exists, insert if not" two-step fallback, for previous versions.
[1] https://github.com/paulchakravarti/pgwrap [2] https://news.ycombinator.com/item?id=11054944
1. An exclusive lock on the table, which is slow as hell 2. A PGPSQL function that attempts to INSERT, catches the duplicate key exception and instead UPDATEs (also handling the case that the row vanishes), which is slower than hell
This is why ON CONFLICT is such a huge deal. If it were as simple as checking keys with a SELECT then sending some to INSERT and some to UPDATE it would just be nice syntax sugar.
That's not true, at least not when you're operating on a single row at a time. According to my benchmarks the function approach was usually within 10% of the native upsert -- even outperforming it by around 5% depending on data distribution. For insert-or-select (instead of update) the function consistently outperformed INSERT ... IGNORE.
So am I correct in my observation that this is just a different way to use/call the psycopg2 library?
The more I look at the code, the more I wonder where is this going? Will the psycopg2 requirement become a problem or a benefit over time?
SQLalchemy is pretty darn good.... there are more ways to connect to a DB in python than I shake a stick at. I guess I am confused...
Edit: Fixed wording on DBAPI
psycopg2 is an implementation of DBAPI2 (with extensions). As the name denotes, DBAPI is an API which database libraries ought implement, it's not a library.
That seems inefficient. You're always going to have the same columns in each row, so storing column names for each row is a huge waste of memory. Django's "cursor.fetchall" returns a list of tuples, which seems to be a saner way to represent db rows in Python.
This lib also looks brand new, so there is plenty of time for the community to optimize the hell out of it.
>>> d = {'ñanáöß$': 1} >>> d {'ñanáöß$': 1} >>> d.keys() dict_keys(['ñanáöß$']) >>>
>>> collections.namedtuple("test", "$")
Traceback (most recent call last):
...
ValueError: Type names and field names can only contain alphanumeric characters and underscores: '$'The parent comments were discussing why choosing dicts over namedtuples for db-originated columns, and the fact that field names are restricted on namedtuples but not on dicts is a very compelling point.
Making examples usind dicts and explaining why namedtuples have restrictions is completely missing the point.
>>> d = {'ñanáöß$': 1}
d {'ñanáöß$': 1}
>>> d.keys()
dict_keys(['ñanáöß$'])Harry Percival Looks nice! Y u no namedtuple?
Kenneth Reitz Dictionaries are a much more predictable interface ;)
Kenneth Reitz Just because you can do something, doesn't mean you should!
>>> nt = namedtuple('nt', 'a b')
>>> nt10k = [nt(1, 2) for i in range(10000)]
>>> dict10k = [{'a':1, 'b':2} for i in range(10000)]
>>> timeit pickle.dumps(nt10k)
10 loops, best of 3: 26.3 ms per loop
>>> timeit pickle.dumps(dict10k)
100 loops, best of 3: 2.49 ms per loop[1] https://github.com/python/cpython/blob/master/Lib/collection...
[1] https://github.com/python/cpython/blob/2.7/Python/ceval.c#L1...
[2] https://github.com/python/cpython/blob/master/Python/ceval.c...
If you do a query that returns 100 records and use rows.next(), does it just get them from the cursor without pulling down the rest from the database? Do you need to dispose of the cursor or is it automatically cleaned up?
The export formats are just a an attribute, not a method call. It would need to use @property IIRC. Does this go against the guideline in Python that "explicit is better than implicit"?
When a database query is executed, the Psycopg cursor usually
fetches all the records returned by the backend, transferring
them to the client process. If the query returned an huge amount
of data, a proportionally large amount of memory will be
allocated by the client.
[1] https://github.com/kennethreitz/records/blob/master/records....[2] https://github.com/kennethreitz/records/blob/master/records....
[3] http://initd.org/psycopg/docs/usage.html#server-side-cursors
A psql shell doesn't have the power of python once I get the results, and SQLAlchemy isn't as trivial to use.
Edit: actually it just works on postgres. I don't know if there are plans to make it work on other SQL databases.
I would say 'no'. SQLAlchemy is very good, but extremely complex and powerful. I think people don't understand that you can use the core features without any of the ORM overhead, if you don't want it.
If you want to "Just write SQL" you can do that, easily: http://docs.sqlalchemy.org/en/latest/core/tutorial.html#usin...
I think jumping into that example directly is probably kind of disorienting. The SQL in that string is a little bit weird/contorted just to illustrate, "it's text, do whatever you want!". I also wrote that example like ten years ago.
pyscopg2 is pretty great. Every lib I've seen is based on it. Many apps use it directly. It is the lowest, comfortable layer you'd want. What you need on top of that is Either;
1) So minor, most just write a few helper functions/classes
2) So elaborate and opinionated it needs years of use/work to get right. SQLAlchmey, Django ORM.
3) So specific, customized to your requirements that general purpose libs/frameworks don't fit.
Some people get tired of redoing #1 or don't like #2's opinions or think there should be something lighter. So, they build something they hope to be middle ground. I don't think there is a middle ground. As you use it you realize the complexity (dbs/SQL are much, much more complicated/powerful than you think, the impedance between relations and objects) and middle ground projects remain "toys" or grow into SQLAlchemy.I spent a couple of hours looking for exactly this yesterday for a new Tornado based project and ended up using queries: https://github.com/gmr/queries which seems on the same level for my purposes of wanting to cleanly pass raw SQL and get python data structures in return. Queries also provides asynchronous API interaction with Tornado which will be nice for me.
Coincidently queries was inspired by Kenneth Reitz's work on requests.
I think it is often tempting to write something like this as it is quite a light thing and can reacquaint someone with SQL if they haven't used it for a while. I know I was tempted after pouring through ORMs.
No, but they're meant for other use-cases. This fills in a gap for "simple, but powerful", as advertised. :)
>>> db = records.Database('postgres://localhost:5432')
>>> rows = db.query('select * from customer_customer')
Traceback (most recent call last): File "<console>", line 1, in <module> File "/Users/ns/teabox_django_env/lib/python2.7/site-packages/records.py", line 109, in query c.execute(query, params) File "/Users/ns/teabox_django_env/lib/python2.7/site-packages/psycopg2/extras.py", line 223, in execute return super(RealDictCursor, self).execute(query, vars) ProgrammingError: relation "customer_customer" does not exist LINE 1: select * from customer_customer
This sounds nice, but I'm skeptical.
There is an another (slightly older) Clojure library - https://github.com/ray1729/sql-phrasebook
This was based on the venerable Perl CPAN module - https://metacpan.org/pod/Data::Phrasebook::SQL
Also an interesting read - http://www.perl.com/pub/2002/10/22/phrasebook.html | http://ootips.org/yonat/patterns/phrasebook.html
def query(connection, q):
cursor = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)
cursor.execute(q)
return iter(cursor.fetchone, None)
# db = records.Database('postgres://...')
db = psycopg2.connect('postgres://...')
# rows = db.query('select * from active_users')
rows = query(db, 'select * from active_users')
# rows.next()
next(rows)
# for row in rows:
# spam_user(name=row['name'], email=row['user_email'])
for row in rows:
spam_user(name=row['name'], email=row['user_email'])
# rows.all()
list(rows)https://github.com/catherinedevlin/ipython-sql
Bonus points for native Pandas integration.
In general, I think most Python developers would be happier with Clojure or JavaScript written in a functional style (e.g. heavy use of tcomb, Immutable.js, and transducers). I'm happier, at least.
https://pypi.python.org/pypi/pyDAL
Small example of use:
http://jugad2.blogspot.in/2014/12/pydal-pure-python-database...
You would just require the single redbean.php file, and you could query your database. The fact that this library is similar is clearly a great sign!
EDIT: I'm not saying this is a waste of time from a developer who obviously puts out quality projects. I'm saying I don't see myself using it. I'm most familiar and comfortable with the Django ORM and have overseen a large project with it. There were several raw queries when I took over but once I left it there were none and all the replacement I did was clean ORM queries more performant than their raw SQL predecessors.
That's the catch right there.
AppComment.objects.annotate(content_len=Length('content')).all().aggregate(Sum('content_len'))
https://docs.djangoproject.com/en/1.9/ref/models/expressions...
I am pretty familiar with the Django ORM where you hit limitations fairly early (if you are writing any moderately complex queries). But I still use it for maybe 90% of the stuff I do because that 90% involves pretty simple queries, and it saves a ton of typing, and integrates well with the rest of the Django ecosystem.
Updating data is a good example. I recently split up some database strings into multiple columns. At first I just wrote the SQL to update a joined table. Then I needed to get regexes involved to pull out certain parts of the string. Doing that in SQL gets ugly, and its far simpler just to use the ORM to pull out the data required and operate on it in Python on a loop.
I don't see the ORM as an either / or, but as a complimentary tool to SQL.
It does have the overhead of having to learn the ORM, but once you do that, it pays off.
RDBMS <-> Objects are a poor match. So much so, many pursued NoSQL to find relief from the pain.
http://gitweb.saurik.com/cyql.git
In addition to .run (which returns the number of affected rows) and .all (which returns an array of ordered dicts?), I have .has (which returns a bool and wraps an exists query), .gen (which returns a generator and iterates a cursor), and .one (which verifies you only got back a single row and returns just that row). I also have easy support for transactions (either on an existing connection or in one line as part of making a connection), easily turning off synchronous commit (for log tables), and I carefully manage autocommit to make certain that I am using the minimum number of possible SQL statements to the server (which I care about a lot).
http://gitweb.saurik.com/cyql.git/blob/HEAD:/__init__.py
That said, I agree with the post by masklinn below: neither the linked library nor my library are solving problems that I believe are worth inheriting code from someone else over. There is some basic configuration of psycopg2 which is necessary, but the underlying library itself is what works here. I have spent over a decade thinking about how I like to build SQL interfaces, and have now implemented a similar interface for myself in numerous languages, each time evolving the design slightly (and sometimes having enough of an epiphany that I go back and retrofit some of the older ones), but it ends up being built around the way I think about stuff.
https://news.ycombinator.com/item?id=11053877
And that also means that as I learn more and "level up", I start making different decisions. My implementation in Clojure stresses stored procedures a lot more, as while it took me a long time to really figure out how to use them in my workflow, I now see them as exceedingly correct and feel a lot of the code I've written in the past where I had tons of free statements is essentially "what I wrote from back when I didn't know how to use the database to organize my API layer" (though I still haven't worked out some of the tooling around shifting to stored procedures, and have been distracted with other higher-level problems the last couple years).
Essentially, I'm arguing that the same will happen to you. Put differently: some problems are hard, and some problems are easy; I find a lot of libraries that seem to be solving easy problems that new developers think are hard, and a lot of libraries that pretend to solve a hard problem, but only because the problem looked easy and the result doesn't actually work (such as the PostgreSQL drivers that were available in Ruby for a long time, which were all unusably bad). Wrapping something that works well so it is slightly easier for you to use can be valuable if it is upstreamed into the original project, but even then is likely to be something you will paper over yourself in time as you will think about the problem differently than they did.
# at the top of the code somewhere
dsn = {'port': ..., 'user': '...', 'password': '...', 'database': '...'}
with cyql.connect(dsn) as sql:
provider, account, key = sql.one('''
select
"payment"."provider",
"payment"."account",
"payment"."transaction"
from "cydia"."payment"
where
"payment"."id" = %(payment_id)s
''')
with cyql.connect(dsn) as sql:
sql.run('''
update "cydia"."token" set
"token" = %(token)s,
"email" = %(email)s,
"country" = %(country)s,
"shipping" = %(shipping)s,
"billing" = %(billing)s,
"data" = %(data)s
where
"id" = %(token_id)s and
"token" is null
''')Also, the intersection of SQLite, MySQL, and Postgres is a pretty terrible database. You can be a lot more effective if you decide which one you're writing for.
A lot of CRUD apps (which, I think, a lot of websites/webapps are) don't usually use anything but the basic SQL stuff.
Only if you deploy directly to production!
Any competent organization should have at least a staging environment (and probably some other pre-staging testing environments) where you deploy and run your full application stack, and only promoted verified builds to production after they pass QA on earlier environments.
I.e. one writes plain SQL strings, but they're parsed under the hood, so AST transformations can be applied, like adding extra WHERE clauses conditionally or applying LIMIT.
Sadly, I haven't found any good Python SQL parsing library. That was long time ago, though - maybe someone had written one already.
[1] Peewee: https://github.com/coleifer/peewee
I can't give SQLAlchemy (Core or ORM) a string like "SELECT * FROM foo AS f LEFT JOIN bar AS b ON b.id = f.bar_id WHERE f.baz > %(baz)s" and then transform it, by, say, appending the LIMIT clause or adding extra WHERE condition. AFAIK, there's no way to provide a raw SQL string and then say something like `query.where("NOT f.fnord")` OR `query.limit(10)` and get the updated SQL.
With ORMs or non-object-mapping wrappers if I want transformations, I have to use their own language instead of SQL. I do, but don't really want to.
Or things have changed and this is what SA can do this nowadays? I'll be more than happy to learn that I'm wrong.
If you specify the columns involved with your text query, then you get back a 'TextAsFrom' object, and you can apply other transformations to it like .where() or .unique().
I used to do exactly this kind of filtering, by building up by SA query, back in the day (about 2008) when I last used SA in anger.
there are certainly SQL parsers that can easily produce such tokenized structures and from a technical standpoint, your API is pretty simple to produce, with or without shallow or deep SQLAlchemy integrations. It's just there's not really any interest in such a system and it's never been requested.
There's a very big chance that SQLAlchemy will be integrated into the project, to allow for connections to multiple database types.
* Simplified handling of connections/cursor
* Connection pool (provided by psycopg2.pool)
* Cursor context handler
* Python API to wrap basic SQL functionality
* Simple select,update,delete,join methods extending the cursor
context handler (also available as stand-alone methods which
create an implicit cursor for simple queries)
* Query results as dict (using psycopg2.extras.DictCursor or any other PG Cursor factory)
* Callable prepared statements
* Logging support
Essentially you can do stuff like: >>> import pgwrap
>>> db = pgwrap.connection(url='postgres://localhost')
>>> with db.cursor() as c:
... c.query('select version()')
[['PostgreSQL...']]
>>> v = db.query_one('select version()')
>>> v
['PostgreSQL...']
>>> v.items()
[('version', 'PostgreSQL...')]
>>> v['version']
'PostgreSQL...'
>>> db.create_table('t1','id serial,name text,count int')
>>> db.create_table('t2','id serial,t1_id int,value text')
>>> db.log = sys.stdout
>>> db.insert('t1',{'name':'abc','count':0},returning='id,name')
INSERT INTO t1 (name) VALUES ('abc') RETURNING id,name
[1, 'abc']
>>> db.insert('t2',{'t1_id':1,'value':'t2'})
INSERT INTO t2 (t1_id,value) VALUES (1,'t2')
1
>>> db.select('t1')
SELECT * FROM t1
[[1, 'abc', 0]]
>>> db.select_one('t1',where={'name':'abc'},columns=('name','count'))
SELECT name, count FROM t1 WHERE name = 'abc'
['abc', 0]
>>> db.join(('t1','t2'),columns=('t1.id','t2.value'))
SELECT t1.id, t2.value FROM t1 JOIN t2 ON t1.id = t2.t1_id
[[1, 't2']]
>>> db.insert('t1',{'name':'abc'},returning='id')
INSERT INTO t1 (name) VALUES ('abc') RETURNING id
[2]
>>> db.update('t1',{'name':'xyz'},where={'name':'abc'})
UPDATE t1 SET name = 'xyz' WHERE name = 'abc'
2
>>> db.update('t1',{'count__func':'count + 1'},where= {'count__lt':10},returning="id,count")
UPDATE t1 SET count = count + 1 WHERE count < 10 RETURNING id,count
[[1, 1]]
Also it allows you to create callable prepared statements (which I find really useful in structuring apps): >>> update_t1_name = db.prepare('UPDATE t1 SET name = $2 WHERE id = $1')
PREPARE stmt_001 AS UPDATE t1 SET name = $2 WHERE id = $1
>>> update_t1_name(1,'xxx')
EXECUTE _pstmt_001 (1,'xxx')
[1] https://github.com/paulchakravarti/pgwrap
[2] https://pypi.python.org/pypi/pgwrap