[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