psycopg2: how to pass VARIADIC ARGS? - python

I'm trying to call the function json_extract_path which accepts variadic arguments. Does psycopg2 support queries of the form f(%s, %s, ...), which would accept a variable number of parameters?

The SQL format answer works, but I think it's far more complicated than you need. Just add the VARIADIC keyword and send your arguments as a list. Using the example from json_extract_path:
>>> cur.execute('''SELECT json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}', VARIADIC %s)''', [['f4', 'f6']])
>>> cur.fetchall()
[('foo',)]

The answer is... mostly no, but kinda yes.
psycopg2 has no placeholder for variable arguments, AFAIK it doesn't parse the "format string" at all so it has no idea about context of use, it will just dumbly apply "data" escaping and formatting rules to individual parameters, so you need to create an SQL / format string with the correct number of placeholders, then flatten your sequence as "top-level" parameters.
However psycopg2 2.7 added a psycopg2.sql module which assists with "dynamic" SQL compositions. What you'd do here is use those facilities to generate your query e.g.
path = ['a', 'b', 'c']
cursor.execute(
sql.SQL('select json_extract_path(col, {}) from table').format(
sql.SQL(', ').join(sql.Placeholder() * len(path))
),
[*path]
)
(nb: code untested)
SQL.format validates that the formatted parameters are Composable, so they have been marked a safe explicitly
sql.Placeholder() is a Composable version of %s (it can take an optional name), as you can see it also supports "splatting" specifically for the case where you have a variable number of parameters e.g. VALUES enumeration or function calls
SQL.join is a Composable version of str.join
then you can just pass in your parameters normally, splatting your sequences to "flatten" them

Related

SQLAlchemy pass parameters with GeoPandas.from_postgis

I want to pass in parameters to a sql query when using GeoPandas from_postgis functionality with SQLAlchemy.
classmethod GeoDataFrame.from_postgis(sql, con, geom_col='geom', crs=None, index_col=None, coerce_float=True, params=None)
I have looked at a previous similar question and also here which suggests to use SQLAlchemy's textual SQL option. However, this requires access to con.execute which isn't included in the GeoDataFrame from_postgis option.
Could you suggest the best way to pass the parameters to SQL? If not directly in from_postgis, then how best to construct the full SQL string separately and passing it in as the first sql argument to from_postgis.
For textual SQL, you can add parameters by using .bindparams:
query = text("select * from foo where bar = :a").bindparams(a=1)
For queries you construct in SQLAlchemy, bind parameters are automatically included:
foo = Table(...) # you need to define foo
query = select(["*"]).select_from(foo).where(foo.c.bar == 1)
You can also directly pass parameters via the params parameter of from_postgis, if that's more natural:
df.from_postgis(text("select * from foo where bar = :a"), params={"a": 1})
Do not use str.format as the other answer suggests because it's vulnerable to SQL injection.

safe parameter bindings in sqlalchemy filter

I need to pass a partial raw sql query into sqlalchemy filter, like
s.query(account).filter("coordinate <#> point(%s,%s) < %s"%(lat,long,distance))
Yes, I'm trying to use earthdistance function in postgresql.
Of course, I could use PostGis and GeoAlchemy2, but I want to know the general solution to this kind of problems.
I know sqlalchemy can safely pass raw sql query .
result = db.engine.execute("select * coordinate <#> point(:lat,:long) < :distance",**params)
Is there any similar function that can be used to bind parameter of partial(?) sql query? I guess someone who implements custom sql function like func.ll_to_earth have used the function.
There is .params() on query. Try this:
query = s.query(account).filter(
"coordinate <#> point(:lat, :long_) < :dist").params(
lat=lat, long_=long_, dist=distance)
And there is the documentation on it.
Note: I renamed your long param, because there is alread a __builtin__ named long (long int) in python, it's good practice to not overwrite already used words for obvious reasons.

Why would unpacking be preffered over passing in a list

Unpacking argument lists:
def send(*data):
for datum in data:
ser.write(datum)
vs sending in a list in the first place:
def send(data):
for datum in data:
ser.write(datum)
When it simplifies the API for the case where otherwise you'll always have to pass in a list:
send(something, otherthing)
versus:
send([something, otherthing])
where your usual parameters are taken from different locations; e.g. something and otherthing are more likely to be separate variables than already collected in one list.
The Python 3.x print() function does exactly that, as well as the os.path.join() function. You rarely have all your print arguments or path-elements-to-join combined in a list before calling the API.
Compare:
os.path.join(rootdirectory, relativepath, filename)
print('Debug information:', localvariable)
vs.
os.path.join([rootdirectory, relativepath, filename])
print(['Debug information:', localvariable])
If .join() or print() were to accept only one positional argument (a list), the users of the API would find themselves typing the [ and ] brackets over and over again.
By accepting a variable number of positional arguments, you save users of your API the trouble of having to create list just for the function call. In the rare cases where the parameters have already been collected into a list, they can use the *params calling convention:
send(*params)
to echo your function signature.

RIGHT() function in sqlalchemy

Can I implement the following in SQLAlchemy,
SELECT *
FROM TABLE
WHERE RIGHT(COLUMN_CODE, 2) = 'AX'
here RIGHT( ) returns the right part of a character string with the specified number of characters.
Is there a SQLAlchemy implementation of the RIGHT function?
You'd be better off using the .endswith() method instead:
select([tabledef]).where(tabledef.c.column_code.endswith('AX'))
or, when filtering with a mapper config and a session:
session.query(mappedobject).filter(mappedobject.column_code.endswith('AX'))
The column_code.endswith() method will be translated to whatever SQL is best for your particular engine, matching column values that end with AX.
You can always use the function generator to create arbitrary SQL functions if you have to use the RIGHT() sql function directly:
from sqlalchemy.sql.expression import func
select([tabledef]).where(func.right(tabledef.c.column_code, 2) == 'AX')
and the func.right() call will be translated to RIGHT(column_code, 2) by the SQL generation layer.
The documentation does not make it clear, but you can write any function using func.funcname sytle. funcname does not have to be defined natively by SQLAlchemy module. SQLAlchemy knows about common functions like min, max etc. and if there is dialect to dialect variation amongst those functions, SQLAlchemy takes care of that for you.
But the functions that SQLAlchemy does not know about are passed as is. So you can create your query that generates a SQL statement with the required RIGHT function like so
>>> from sqlalchemy import func
>>> select([table]).where(func.RIGHT(users.c.column_code, 2)='AX')

How should I convert a Python tuple of strings into dynamically-specified types?

I'm writing a simple Python application using the cmd module to provide a CLI-type interface. The commands provided by my CLI have parameter lists that vary widely. Each command handler receives a string argument containing the portion of the line that contains arguments; I plan to tokenize them into a tuple using shlex.split. Subsequently, I'm looking for the most Pythonic way to take that tuple of strings, validate that they are well-formed, and convert them into a tuple of cleanly-specified numeric types.
Example: I have a function foo that takes 3 arguments: the first is a path to a file on disk, the second is a floating-point value, and the third is an integer, like:
foo /home/jason/file.bin 123.456 123456
I'd like a clean way of specifying this, something akin to using C's sscanf() with a format string of "%s %f %d" (I understand the whitespace-handling issues inherent in that approach; it's just an illustration).
I know that I can accomplish this by writing boilerplate code for each handler function that calls int(), float(), etc. and catches exceptions appropriately. It just seems that there should be a cleaner way of doing this.
I would suggest providing the production rules as functions that parse the arguments, and raise an exception for invalid arguments. so. your example might look like this:
FOO_SYNTAX = (file, float, int)
def foo_cmd(infile, infloat, inint):
pass
def parse_args(rule, args):
if len(rule) != len(args):
raise ValueError, "Wrong number of arguments"
return [rule_item(arg) for rule_item, arg in zip(rule, args)]
COMMANDS = {'foo': (FOO_SYNTAX, foo_cmd)}
def dispatch(line):
cmd, rest = line.split(None, 1)
args = rest.split()
syntax, cmd_func = COMMANDS[cmd]
cmd_func(*parse_args(syntax, args))
Depending on whether you are using Python 2.6 or 2.7, you could use the built in optparse or argparse, respectively.
http://docs.python.org/library/argparse.html
They may be slightly heavyweight, but they'll do conversion to ints,floats, or whatever type you need as part of the parsing, and it can automatically build a usage message and other nice argument parsing things.

Categories

Resources