]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Adding a first implementation of the libpq wrapper and tests
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 13 Mar 2020 10:27:19 +0000 (23:27 +1300)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 13 Mar 2020 10:27:19 +0000 (23:27 +1300)
14 files changed:
.gitignore
README.rst [new file with mode: 0644]
psycopg3/__init__.py [new file with mode: 0644]
psycopg3/_pq_ctypes.py [new file with mode: 0644]
psycopg3/consts.py [new file with mode: 0644]
psycopg3/pq.py [new file with mode: 0644]
psycopg3/pq_ctypes.py [new file with mode: 0644]
psycopg3/pq_enums.py [new file with mode: 0644]
pyproject.toml [new file with mode: 0644]
setup.py [new file with mode: 0644]
tests/__init__.py [new file with mode: 0644]
tests/conftest.py [new file with mode: 0644]
tests/fix_db.py [new file with mode: 0644]
tests/test_pq.py [new file with mode: 0644]

index 0a764a4de3a890dbe2a3336c648f7f6d1892c132..31577c5a4f84d163079576426f0259def37a9ae6 100644 (file)
@@ -1 +1,2 @@
 env
+/psycopg3.egg-info
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..39c79ab
--- /dev/null
@@ -0,0 +1,4 @@
+psycopg3 -- PostgreSQL database adaapter for Python
+===================================================
+
+psycopg3 is a modern implementation of a PostgreSQL adapter for Python.
diff --git a/psycopg3/__init__.py b/psycopg3/__init__.py
new file mode 100644 (file)
index 0000000..ce7a0b6
--- /dev/null
@@ -0,0 +1,9 @@
+"""
+psycopg3 -- PostgreSQL database adaapter for Python
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+from .consts import VERSION as __version__
+
+__all__ = ["__version__"]
diff --git a/psycopg3/_pq_ctypes.py b/psycopg3/_pq_ctypes.py
new file mode 100644 (file)
index 0000000..bfa0ad9
--- /dev/null
@@ -0,0 +1,31 @@
+"""
+libpq access using ctypes
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+import ctypes
+import ctypes.util
+from ctypes import Structure, POINTER
+from ctypes import c_char_p, c_int
+
+pq = ctypes.pydll.LoadLibrary(ctypes.util.find_library("pq"))
+
+
+class PGconn(Structure):
+    _fields_ = []
+
+
+PGconn_ptr = POINTER(PGconn)
+
+PQconnectdb = pq.PQconnectdb
+PQconnectdb.argtypes = [c_char_p]
+PQconnectdb.restype = PGconn_ptr
+
+PQstatus = pq.PQstatus
+PQstatus.argtypes = [PGconn_ptr]
+PQstatus.restype = c_int
+
+PQerrorMessage = pq.PQerrorMessage
+PQerrorMessage.argtypes = [PGconn_ptr]
+PQerrorMessage.restype = c_char_p
diff --git a/psycopg3/consts.py b/psycopg3/consts.py
new file mode 100644 (file)
index 0000000..ae40387
--- /dev/null
@@ -0,0 +1,7 @@
+"""
+psycopg3 package constants
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+VERSION = "2.99.0"
diff --git a/psycopg3/pq.py b/psycopg3/pq.py
new file mode 100644 (file)
index 0000000..63332fe
--- /dev/null
@@ -0,0 +1,18 @@
+"""
+psycopg3 libpq wrapper
+
+This package exposes the libpq functionalities as Python objects and functions.
+
+The real implementation (the binding to the C library) is
+implementation-dependant but all the implementations share the same interface.
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+from .pq_enums import ConnStatusType
+
+from . import pq_ctypes as pq_module
+
+PGconn = pq_module.PGconn
+
+__all__ = ("ConnStatusType", "PGconn")
diff --git a/psycopg3/pq_ctypes.py b/psycopg3/pq_ctypes.py
new file mode 100644 (file)
index 0000000..6f51054
--- /dev/null
@@ -0,0 +1,41 @@
+"""
+libpq Python wrapper using ctypes bindings.
+
+Clients shouldn't use this module directly, unless for testing: they should use
+the `pq` module instead, which is in charge of choosing the best
+implementation.
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+from .pq_enums import ConnStatusType
+
+from . import _pq_ctypes as impl
+
+
+class PGconn:
+    __slots__ = ("pgconn_ptr",)
+
+    def __init__(self, pgconn_ptr):
+        self.pgconn_ptr = pgconn_ptr
+
+    @classmethod
+    def connectdb(cls, conninfo):
+        if isinstance(conninfo, str):
+            conninfo = conninfo.encode("utf8")
+
+        if not isinstance(conninfo, bytes):
+            raise TypeError("bytes expected, got %r instead" % conninfo)
+
+        pgconn_ptr = impl.PQconnectdb(conninfo)
+        return cls(pgconn_ptr)
+
+    @property
+    def status(self):
+        rv = impl.PQstatus(self.pgconn_ptr)
+        return ConnStatusType(rv)
+
+    @property
+    def error_message(self):
+        # TODO: decode
+        return impl.PQerrorMessage(self.pgconn_ptr)
diff --git a/psycopg3/pq_enums.py b/psycopg3/pq_enums.py
new file mode 100644 (file)
index 0000000..338063c
--- /dev/null
@@ -0,0 +1,12 @@
+"""
+libpq enum definitions for psycopg3
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+from enum import IntEnum
+
+
+class ConnStatusType(IntEnum):
+    CONNECTION_OK = 0
+    CONNECTION_BAD = 1
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644 (file)
index 0000000..a8f43fe
--- /dev/null
@@ -0,0 +1,2 @@
+[tool.black]
+line-length = 79
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..240e70b
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+"""
+psycopg3 -- PostgreSQL database adaapter for Python
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+
+import re
+import os
+from setuptools import setup
+
+# Grab the version without importing the module
+# or we will get import errors on install if prerequisites are still missing
+fn = os.path.join(os.path.dirname(__file__), "psycopg3/consts.py")
+with open(fn) as f:
+    m = re.search(r"""(?mi)^VERSION\s*=\s*["']+([^'"]+)["']+""", f.read())
+if m:
+    version = m.group(1)
+else:
+    raise ValueError("cannot find VERSION in the consts module")
+
+# Read the description from the README
+with open("README.rst") as f:
+    readme = f.read()
+
+# TODO: classifiers
+classifiers = """
+Programming Language :: Python :: 3
+Topic :: Database
+Topic :: Software Development
+"""
+
+setup(
+    name="psycopg3",
+    description=readme.splitlines()[0],
+    long_description="\n".join(readme.splitlines()[2:]).lstrip(),
+    author="Daniele Varrazzo",
+    author_email="daniele.varrazzo@gmail.com",
+    url="https://psycopg.org/psycopg3",
+    license="TBD",  # TODO
+    python_requires=">=3.6",
+    packages=["psycopg3"],
+    classifiers=[x for x in classifiers.split("\n") if x],
+    zip_safe=False,
+    version=version,
+)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644 (file)
index 0000000..16040b1
--- /dev/null
@@ -0,0 +1 @@
+pytest_plugins = ("tests.fix_db",)
diff --git a/tests/fix_db.py b/tests/fix_db.py
new file mode 100644 (file)
index 0000000..44afd3c
--- /dev/null
@@ -0,0 +1,29 @@
+import os
+import pytest
+
+
+def pytest_addoption(parser):
+    parser.addoption(
+        "--test-dsn",
+        metavar="DSN",
+        default=os.environ.get("PSYCOPG3_TEST_DSN") or None,
+        help="Connection string to run database tests requiring a connection"
+        " [you can also use the PSYCOPG3_TEST_DSN env var].",
+    )
+
+
+@pytest.fixture
+def pq():
+    """The libpq module wrapper to test."""
+    from psycopg3 import pq
+
+    return pq
+
+
+@pytest.fixture()
+def dsn(request):
+    """Return the dsn used to connect to the `--test-dsn` database."""
+    dsn = request.config.getoption("--test-dsn")
+    if not dsn:
+        pytest.skip("skipping test as no --test-dsn")
+    return dsn
diff --git a/tests/test_pq.py b/tests/test_pq.py
new file mode 100644 (file)
index 0000000..fb0bebb
--- /dev/null
@@ -0,0 +1,22 @@
+import pytest
+
+
+def test_PQconnectdb(pq, dsn):
+    conn = pq.PGconn.connectdb(dsn)
+    assert conn.status == pq.ConnStatusType.CONNECTION_OK, conn.error_message
+
+
+def test_PQconnectdb_bytes(pq, dsn):
+    conn = pq.PGconn.connectdb(dsn.encode("utf8"))
+    assert conn.status == pq.ConnStatusType.CONNECTION_OK, conn.error_message
+
+
+def test_PQconnectdb_error(pq):
+    conn = pq.PGconn.connectdb("dbname=psycopg3_test_not_for_real")
+    assert conn.status == pq.ConnStatusType.CONNECTION_BAD
+
+
+@pytest.mark.parametrize("baddsn", [None, 42])
+def test_PQconnectdb_badtype(pq, baddsn):
+    with pytest.raises(TypeError):
+        pq.PGconn.connectdb(baddsn)