A first run at this:
import psycopg2
from psycopg2 import sql
name = 'test'
columns = [('id', ' serial PRIMARY KEY,'), ('name', ' text')]
composed_cols = []
for col in columns:
composed_cols.append(sql.Composed([sql.Identifier(col[0]), sql.SQL(col[1])]))
[Composed([Identifier('id'), SQL(' serial PRIMARY KEY,')]),
Composed([Identifier('name'), SQL(' text')])]
qry = sql.SQL("CREATE TABLE {name} ({} {})").format(sql.SQL('').join(composed_cols[0]), sql.SQL('').join(composed_cols[1]), name=sql.Identifier(name))
print(qry.as_string(con))
CREATE TABLE "test" ("id" serial PRIMARY KEY, "name" text)
cur.execute(qry)
con.commit()
\d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+----------------------------------
id | integer | | not null | nextval('test_id_seq'::regclass)
name | text | | |
Indexes:
"test_pkey" PRIMARY KEY, btree (id)
Basically break the column definition into two components, the name/identifier and the type/constraint portion. Then create a list that has these elements composed into the correct sql objects. Build the query string by joining the elements of the list into the {} placeholders for the column name and type/constraint portions respectively. Use named placeholder {name} for the table name. The portion that needs to be paid attention to is the sql.SQL as that is a literal string and if it is coming from an outside source it would need to be validated.
UPDATE
Realized this could be simplified as:
col_str = '(id serial PRIMARY KEY, name text)'
qry = sql.SQL("CREATE TABLE {name} {cols} ").format(cols=sql.SQL(col_str), name=sql.Identifier(name))
print(qry.as_string(con))
CREATE TABLE "test" (id serial PRIMARY KEY, name text)
sql.SQLnot justSQL. Also you cannot pass multiple columns into one place holder.