--- /dev/null
+psycopg3 -- PostgreSQL database adaapter for Python
+===================================================
+
+psycopg3 is a modern implementation of a PostgreSQL adapter for Python.
--- /dev/null
+"""
+psycopg3 -- PostgreSQL database adaapter for Python
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+from .consts import VERSION as __version__
+
+__all__ = ["__version__"]
--- /dev/null
+"""
+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
--- /dev/null
+"""
+psycopg3 package constants
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+VERSION = "2.99.0"
--- /dev/null
+"""
+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")
--- /dev/null
+"""
+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)
--- /dev/null
+"""
+libpq enum definitions for psycopg3
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+from enum import IntEnum
+
+
+class ConnStatusType(IntEnum):
+ CONNECTION_OK = 0
+ CONNECTION_BAD = 1
--- /dev/null
+[tool.black]
+line-length = 79
--- /dev/null
+#!/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,
+)
--- /dev/null
+pytest_plugins = ("tests.fix_db",)
--- /dev/null
+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
--- /dev/null
+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)