[Python-modules-commits] [python-pgspecial] 02/08: Import python-pgspecial_1.4.0.orig.tar.gz
ChangZhuo Chen
czchen at moszumanska.debian.org
Wed Jun 29 15:01:14 UTC 2016
This is an automated email from the git hooks/post-receive script.
czchen pushed a commit to branch master
in repository python-pgspecial.
commit d5ee653e10c82d7d73e97e1350bfd0e78055e25e
Author: ChangZhuo Chen (陳昌倬) <czchen at debian.org>
Date: Wed Jun 29 22:35:00 2016 +0800
Import python-pgspecial_1.4.0.orig.tar.gz
---
MANIFEST.in | 1 +
PKG-INFO | 2 +-
pgspecial.egg-info/PKG-INFO | 2 +-
pgspecial.egg-info/SOURCES.txt | 12 +-
pgspecial.egg-info/requires.txt | 1 +
pgspecial/__init__.py | 2 +-
pgspecial/dbcommands.py | 75 +++++++++++
pgspecial/iocommands.py | 58 ++++++++
setup.py | 1 +
tests/.cache/v/cache/lastfailed | 3 +
.../test_specials.cpython-27-PYTEST.pyc | Bin 0 -> 16944 bytes
tests/__pycache__/test_tm.cpython-27-PYTEST.pyc | Bin 0 -> 2301 bytes
tests/conftest.py | 36 +++++
tests/conftest.pyc | Bin 0 -> 1573 bytes
tests/dbutils.py | 72 ++++++++++
tests/dbutils.pyc | Bin 0 -> 2591 bytes
tests/pytest.ini | 2 +
tests/test_specials.py | 149 +++++++++++++++++++++
18 files changed, 412 insertions(+), 4 deletions(-)
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..37f630a
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+recursive-include tests *
diff --git a/PKG-INFO b/PKG-INFO
index e5c5a4a..2f14cab 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: pgspecial
-Version: 1.3.0
+Version: 1.4.0
Summary: Meta-commands handler for Postgres Database.
Home-page: http://pgcli.com
Author: Amjith Ramanujam
diff --git a/pgspecial.egg-info/PKG-INFO b/pgspecial.egg-info/PKG-INFO
index e5c5a4a..2f14cab 100644
--- a/pgspecial.egg-info/PKG-INFO
+++ b/pgspecial.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: pgspecial
-Version: 1.3.0
+Version: 1.4.0
Summary: Meta-commands handler for Postgres Database.
Home-page: http://pgcli.com
Author: Amjith Ramanujam
diff --git a/pgspecial.egg-info/SOURCES.txt b/pgspecial.egg-info/SOURCES.txt
index 0a1bb52..b9bccc0 100644
--- a/pgspecial.egg-info/SOURCES.txt
+++ b/pgspecial.egg-info/SOURCES.txt
@@ -1,3 +1,4 @@
+MANIFEST.in
README.rst
setup.py
pgspecial/__init__.py
@@ -12,4 +13,13 @@ pgspecial.egg-info/pbr.json
pgspecial.egg-info/requires.txt
pgspecial.egg-info/top_level.txt
pgspecial/help/__init__.py
-pgspecial/help/commands.py
\ No newline at end of file
+pgspecial/help/commands.py
+tests/conftest.py
+tests/conftest.pyc
+tests/dbutils.py
+tests/dbutils.pyc
+tests/pytest.ini
+tests/test_specials.py
+tests/.cache/v/cache/lastfailed
+tests/__pycache__/test_specials.cpython-27-PYTEST.pyc
+tests/__pycache__/test_tm.cpython-27-PYTEST.pyc
\ No newline at end of file
diff --git a/pgspecial.egg-info/requires.txt b/pgspecial.egg-info/requires.txt
index e86a836..6064409 100644
--- a/pgspecial.egg-info/requires.txt
+++ b/pgspecial.egg-info/requires.txt
@@ -1 +1,2 @@
click >= 4.1
+sqlparse >= 0.1.19
diff --git a/pgspecial/__init__.py b/pgspecial/__init__.py
index 008ca77..ba8ff4b 100644
--- a/pgspecial/__init__.py
+++ b/pgspecial/__init__.py
@@ -1,5 +1,5 @@
__all__ = []
-__version__ = '1.3.0'
+__version__ = '1.4.0'
def export(defn):
diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py
index 127fd93..0390b3c 100644
--- a/pgspecial/dbcommands.py
+++ b/pgspecial/dbcommands.py
@@ -102,6 +102,81 @@ def list_schemas(cur, pattern, verbose):
return [(None, cur, headers, cur.statusmessage)]
+ at special_command('\\dx', '\\dx[+] [pattern]', 'List extensions.')
+def list_extensions(cur, pattern, verbose):
+
+ # Note: psql \dx command seems to ignore schema patterns
+ _, name_pattern = sql_name_pattern(pattern)
+
+ if verbose:
+ extensions = _find_extensions(cur, name_pattern)
+
+ if not extensions:
+ msg = 'Did not find any extension named "%s"' % pattern
+ return [(None, cur, [], msg)]
+
+ results = []
+ for ext_name, oid in extensions:
+ title = 'Objects in extension "%s"' % ext_name
+ cur, headers, status = _describe_extension(cur, oid)
+ results.append((title, cur, headers, status))
+
+ return results
+
+ sql = '''
+ SELECT e.extname AS "Name",
+ e.extversion AS "Version",
+ n.nspname AS "Schema",
+ c.description AS "Description"
+ FROM pg_catalog.pg_extension e
+ LEFT JOIN pg_catalog.pg_namespace n
+ ON n.oid = e.extnamespace
+ LEFT JOIN pg_catalog.pg_description c
+ ON c.objoid = e.oid
+ AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass
+ '''
+
+ if name_pattern:
+ sql = cur.mogrify(sql + ' WHERE e.extname ~ %s', [name_pattern])
+
+ sql += ' ORDER BY 1'
+
+ log.debug(sql)
+ cur.execute(sql)
+ headers = [x[0] for x in cur.description]
+ return [(None, cur, headers, cur.statusmessage)]
+
+
+def _find_extensions(cur, pattern):
+ sql = 'SELECT e.extname, e.oid FROM pg_catalog.pg_extension e'
+
+ if pattern:
+ sql = cur.mogrify(sql + ' WHERE e.extname ~ %s', [pattern])
+
+ sql += ' ORDER BY 1'
+
+ log.debug(sql)
+ cur.execute(sql)
+ return cur.fetchall()
+
+
+def _describe_extension(cur, oid):
+ sql = '''
+ SELECT pg_catalog.pg_describe_object(classid, objid, 0)
+ AS "Object Description"
+ FROM pg_catalog.pg_depend
+ WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass
+ AND refobjid = %s
+ AND deptype = 'e'
+ ORDER BY 1 '''
+ sql = cur.mogrify(sql, [oid])
+ log.debug(sql)
+ cur.execute(sql)
+
+ headers = [x[0] for x in cur.description]
+ return cur, headers, cur.statusmessage
+
+
def list_objects(cur, pattern, verbose, relkinds):
"""
Returns (title, rows, header, status)
diff --git a/pgspecial/iocommands.py b/pgspecial/iocommands.py
index fd90c0e..c3b7966 100644
--- a/pgspecial/iocommands.py
+++ b/pgspecial/iocommands.py
@@ -1,7 +1,11 @@
+from contextlib import contextmanager
import re
+import sys
import logging
import click
import io
+import sqlparse
+import psycopg2
from os.path import expanduser
from .namedqueries import NamedQueries
from . import export
@@ -77,6 +81,60 @@ def read_from_file(path):
return contents
+ at contextmanager
+def _paused_thread():
+ try:
+ thread = psycopg2.extensions.get_wait_callback()
+ psycopg2.extensions.set_wait_callback(None)
+ yield
+ finally:
+ psycopg2.extensions.set_wait_callback(thread)
+
+
+def _index_of_file_name(tokenlist):
+ for (idx, token) in reversed(list(enumerate(tokenlist[:-2]))):
+ if token.is_keyword and token.value.upper() in ('TO', 'FROM'):
+ return idx + 2
+ return None
+
+
+ at special_command('\\copy', '\\copy [tablename] to/from [filename]',
+ 'Copy data between a file and a table.')
+def copy(cur, pattern, verbose):
+ """Copies table data to/from files"""
+
+ # Replace the specified file destination with STDIN or STDOUT
+ parsed = sqlparse.parse(pattern)
+ tokenlist = parsed[0].tokens
+ idx = _index_of_file_name(tokenlist)
+ file_name = tokenlist[idx].value
+ before_file_name = ''.join(t.value for t in tokenlist[:idx])
+ after_file_name = ''.join(t.value for t in tokenlist[idx+1:])
+
+ direction = tokenlist[idx-2].value.upper()
+ replacement_file_name = 'STDIN' if direction == 'FROM' else 'STDOUT'
+ query = u'{0} {1} {2}'.format(before_file_name, replacement_file_name,
+ after_file_name)
+ open_mode = 'r' if direction == 'FROM' else 'w'
+ if file_name.startswith("'") and file_name.endswith("'"):
+ file = io.open(expanduser(file_name.strip("'")), mode=open_mode, encoding='utf-8')
+ elif 'stdin' in file_name.lower():
+ file = sys.stdin
+ elif 'stdout' in file_name.lower():
+ file = sys.stdout
+ else:
+ raise Exception('Enclose filename in single quotes')
+
+ with _paused_thread():
+ cur.copy_expert('copy ' + query, file)
+
+ if cur.description:
+ headers = [x[0] for x in cur.description]
+ return [(None, cur, headers, cur.statusmessage)]
+ else:
+ return [(None, None, None, cur.statusmessage)]
+
+
@special_command('\\n', '\\n[+] [name]', 'List or execute named queries.')
def execute_named_query(cur, pattern, **_):
"""Returns (title, rows, headers, status)"""
diff --git a/setup.py b/setup.py
index 5fbca2d..b93182f 100644
--- a/setup.py
+++ b/setup.py
@@ -23,6 +23,7 @@ setup(
long_description=open('README.rst').read(),
install_requires=[
'click >= 4.1',
+ 'sqlparse >= 0.1.19',
],
classifiers=[
'Intended Audience :: Developers',
diff --git a/tests/.cache/v/cache/lastfailed b/tests/.cache/v/cache/lastfailed
new file mode 100644
index 0000000..0ba23c8
--- /dev/null
+++ b/tests/.cache/v/cache/lastfailed
@@ -0,0 +1,3 @@
+{
+ "test_specials.py::test_create_file": true
+}
\ No newline at end of file
diff --git a/tests/__pycache__/test_specials.cpython-27-PYTEST.pyc b/tests/__pycache__/test_specials.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..7b70013
Binary files /dev/null and b/tests/__pycache__/test_specials.cpython-27-PYTEST.pyc differ
diff --git a/tests/__pycache__/test_tm.cpython-27-PYTEST.pyc b/tests/__pycache__/test_tm.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..59882be
Binary files /dev/null and b/tests/__pycache__/test_tm.cpython-27-PYTEST.pyc differ
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..5de0077
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,36 @@
+import pytest
+from dbutils import (create_db, db_connection, setup_db, teardown_db)
+from pgspecial.main import PGSpecial
+
+
+ at pytest.yield_fixture(scope='module')
+def connection():
+ create_db('_test_db')
+ connection = db_connection('_test_db')
+ setup_db(connection)
+ yield connection
+
+ teardown_db(connection)
+ connection.close()
+
+
+ at pytest.fixture
+def cursor(connection):
+ with connection.cursor() as cur:
+ return cur
+
+
+ at pytest.fixture
+def executor(connection):
+ cur = connection.cursor()
+ pgspecial = PGSpecial()
+
+ def query_runner(sql):
+ results = []
+ for title, rows, headers, status in pgspecial.execute(cur=cur, sql=sql):
+ if rows:
+ results.extend((title, list(rows), headers, status))
+ else:
+ results.extend((title, None, headers, status))
+ return results
+ return query_runner
diff --git a/tests/conftest.pyc b/tests/conftest.pyc
new file mode 100644
index 0000000..6dfaf25
Binary files /dev/null and b/tests/conftest.pyc differ
diff --git a/tests/dbutils.py b/tests/dbutils.py
new file mode 100644
index 0000000..ed16b61
--- /dev/null
+++ b/tests/dbutils.py
@@ -0,0 +1,72 @@
+import pytest
+import psycopg2
+import psycopg2.extras
+
+
+# TODO: should this be somehow be divined from environment?
+POSTGRES_USER, POSTGRES_HOST = 'postgres', 'localhost'
+
+
+def db_connection(dbname=None):
+ conn = psycopg2.connect(user=POSTGRES_USER, host=POSTGRES_HOST,
+ database=dbname)
+ conn.autocommit = True
+ return conn
+
+
+try:
+ conn = db_connection()
+ CAN_CONNECT_TO_DB = True
+ SERVER_VERSION = conn.server_version
+except:
+ CAN_CONNECT_TO_DB = False
+ SERVER_VERSION = 0
+
+
+dbtest = pytest.mark.skipif(
+ not CAN_CONNECT_TO_DB,
+ reason="Need a postgres instance at localhost accessible by user "
+ "'postgres'")
+
+
+def create_db(dbname):
+ with db_connection().cursor() as cur:
+ try:
+ cur.execute('''CREATE DATABASE _test_db''')
+ except:
+ pass
+
+
+def setup_db(conn):
+ with conn.cursor() as cur:
+ # schemas
+ cur.execute('create schema schema1')
+ cur.execute('create schema schema2')
+
+ # tables
+ cur.execute('create table tbl1(id1 integer, txt1 text, CONSTRAINT id_text PRIMARY KEY(id1, txt1))')
+ cur.execute('create table tbl2(id2 integer, txt2 text)')
+ cur.execute('create table schema1.s1_tbl1(id1 integer, txt1 text)')
+
+ # views
+ cur.execute('create view vw1 as select * from tbl1')
+ cur.execute('''create view schema1.s1_vw1 as select * from
+ schema1.s1_tbl1''')
+
+ # datatype
+ cur.execute('create type foo AS (a int, b text)')
+
+ # functions
+ cur.execute('''create function func1() returns int language sql as
+ $$select 1$$''')
+ cur.execute('''create function schema1.s1_func1() returns int language
+ sql as $$select 2$$''')
+
+
+def teardown_db(conn):
+ with conn.cursor() as cur:
+ cur.execute('''
+ DROP SCHEMA public CASCADE;
+ CREATE SCHEMA public;
+ DROP SCHEMA IF EXISTS schema1 CASCADE;
+ DROP SCHEMA IF EXISTS schema2 CASCADE''')
diff --git a/tests/dbutils.pyc b/tests/dbutils.pyc
new file mode 100644
index 0000000..320ad9b
Binary files /dev/null and b/tests/dbutils.pyc differ
diff --git a/tests/pytest.ini b/tests/pytest.ini
new file mode 100644
index 0000000..adb5b6b
--- /dev/null
+++ b/tests/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+addopts=--capture=sys --showlocals -rxs
\ No newline at end of file
diff --git a/tests/test_specials.py b/tests/test_specials.py
new file mode 100644
index 0000000..0f5451c
--- /dev/null
+++ b/tests/test_specials.py
@@ -0,0 +1,149 @@
+ #!/usr/bin/python
+ # -*- coding: utf-8 -*-
+
+from dbutils import dbtest
+import itertools
+from codecs import open
+
+
+ at dbtest
+def test_slash_d(executor):
+ results = executor('\d')
+ title = None
+ rows = [('public', 'tbl1', 'table', 'postgres'),
+ ('public', 'tbl2', 'table', 'postgres'),
+ ('public', 'vw1', 'view', 'postgres')]
+ headers = ['Schema', 'Name', 'Type', 'Owner']
+ status = 'SELECT 3'
+ expected = [title, rows, headers, status]
+ assert results == expected
+
+
+ at dbtest
+def test_slash_d_table(executor):
+ results = executor('\d tbl1')
+ title = None
+ rows = [['id1', 'integer', ' not null'],
+ ['txt1', 'text', ' not null'],
+ ]
+ headers = ['Column', 'Type', 'Modifiers']
+ status = 'Indexes:\n "id_text" PRIMARY KEY, btree (id1, txt1)\n'
+ expected = [title, rows, headers, status]
+ assert results == expected
+
+
+ at dbtest
+def test_slash_dn(executor):
+ """List all schemas."""
+ results = executor('\dn')
+ title = None
+ rows = [('public', 'postgres'),
+ ('schema1', 'postgres'),
+ ('schema2', 'postgres')]
+ headers = ['Name', 'Owner']
+ status = 'SELECT 3'
+ expected = [title, rows, headers, status]
+ assert results == expected
+
+
+ at dbtest
+def test_slash_dt(executor):
+ """List all tables in public schema."""
+ results = executor('\dt')
+ title = None
+ rows = [('public', 'tbl1', 'table', 'postgres'),
+ ('public', 'tbl2', 'table', 'postgres')]
+ headers = ['Schema', 'Name', 'Type', 'Owner']
+ status = 'SELECT 2'
+ expected = [title, rows, headers, status]
+ assert results == expected
+
+
+ at dbtest
+def test_slash_dT(executor):
+ """List all datatypes."""
+ results = executor('\dT')
+ title = None
+ rows = [('public', 'foo', None)]
+ headers = ['Schema', 'Name', 'Description']
+ status = 'SELECT 1'
+ expected = [title, rows, headers, status]
+ assert results == expected
+
+
+ at dbtest
+def test_slash_df(executor):
+ results = executor('\df')
+ title = None
+ rows = [('public', 'func1', 'integer', '', 'normal')]
+ headers = ['Schema', 'Name', 'Result data type', 'Argument data types',
+ 'Type']
+ status = 'SELECT 1'
+ expected = [title, rows, headers, status]
+ assert results == expected
+
+help_rows = [['ABORT', 'ALTER AGGREGATE', 'ALTER COLLATION', 'ALTER CONVERSION', 'ALTER DATABASE', 'ALTER DEFAULT PRIVILEGES'], ['ALTER DOMAIN', 'ALTER EVENT TRIGGER', 'ALTER EXTENSION', 'ALTER FOREIGN DATA WRAPPER', 'ALTER FOREIGN TABLE', 'ALTER FUNCTION'], ['ALTER GROUP', 'ALTER INDEX', 'ALTER LANGUAGE', 'ALTER LARGE OBJECT', 'ALTER MATERIALIZED VIEW', 'ALTER OPCLASS'], ['ALTER OPERATOR', 'ALTER OPFAMILY', 'ALTER POLICY', 'ALTER ROLE', 'ALTER RULE', 'ALTER SCHEMA'], ['ALTER SEQUENCE', [...]
+
+ at dbtest
+def test_slash_h(executor):
+ """List all commands."""
+ results = executor('\h')
+ expected = [None, help_rows, [], None]
+ assert results == expected
+
+ at dbtest
+def test_slash_h_command(executor):
+ """Check help is returned for all commands"""
+ for command in itertools.chain(*help_rows):
+ results = executor('\h %s' % command)
+ assert results[3].startswith('Description\n')
+ assert 'Syntax' in results[3]
+
+ at dbtest
+def test_slash_h_alias(executor):
+ """\? is properly aliased to \h"""
+ h_results = executor('\h SELECT')
+ results = executor('\? SELECT')
+ assert results[3] == h_results[3]
+
+
+ at dbtest
+def test_slash_copy_to_tsv(executor, tmpdir):
+ filepath = tmpdir.join('pycons.tsv')
+ executor(u"\copy (SELECT 'Montréal', 'Portland', 'Cleveland') TO '{0}' "
+ .format(filepath))
+ infile = filepath.open(encoding='utf-8')
+ contents = infile.read()
+ assert len(contents.splitlines()) == 1
+ assert u'Montréal' in contents
+
+
+ at dbtest
+def test_slash_copy_to_stdout(executor, capsys):
+ executor(u"\copy (SELECT 'Montréal', 'Portland', 'Cleveland') TO stdout")
+ (out, err) = capsys.readouterr()
+ assert out == u'Montréal\tPortland\tCleveland\n'
+
+
+ at dbtest
+def test_slash_copy_to_csv(executor, tmpdir):
+ filepath = tmpdir.join('pycons.tsv')
+ executor(u"\copy (SELECT 'Montréal', 'Portland', 'Cleveland') TO '{0}' WITH csv"
+ .format(filepath))
+ infile =filepath.open(encoding='utf-8')
+ contents = infile.read()
+ assert len(contents.splitlines()) == 1
+ assert u'Montréal' in contents
+ assert u',' in contents
+
+
+ at dbtest
+def test_slash_copy_from_csv(executor, connection, tmpdir):
+ filepath = tmpdir.join('tbl1.csv')
+ executor("\copy (SELECT 22, 'elephant') TO '{0}' WITH csv"
+ .format(filepath))
+ executor("\copy tbl1 FROM '{0}' WITH csv".format(filepath))
+ cur = connection.cursor()
+ cur.execute("SELECT * FROM tbl1 WHERE id1 = 22")
+ row = cur.fetchone()
+ assert row[1] == 'elephant'
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-pgspecial.git
More information about the Python-modules-commits
mailing list