diff --git a/.appveyor/packages.yml b/.appveyor/packages.yml new file mode 100644 index 00000000..628475c9 --- /dev/null +++ b/.appveyor/packages.yml @@ -0,0 +1,88 @@ +version : 2.x.{build} + +clone_folder: C:\Project + +# We use the configuration to specify the package name +configuration: + - psycopg2 + - psycopg2-binary + +environment: + matrix: + # For Python versions available on Appveyor, see + # https://www.appveyor.com/docs/windows-images-software/#python + - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019, PY_VER: "39", PY_ARCH: "32"} + - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019, PY_VER: "39", PY_ARCH: "64"} + - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015, PY_VER: "38", PY_ARCH: "32"} + - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015, PY_VER: "38", PY_ARCH: "64"} + - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015, PY_VER: "37", PY_ARCH: "32"} + - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015, PY_VER: "37", PY_ARCH: "64"} + - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015, PY_VER: "36", PY_ARCH: "32"} + - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015, PY_VER: "36", PY_ARCH: "64"} + + WORKFLOW: packages + + OPENSSL_VERSION: "1_1_1h" + POSTGRES_VERSION: "13_0" + + PSYCOPG2_TESTDB: psycopg2_test + PSYCOPG2_TESTDB_USER: postgres + PSYCOPG2_TESTDB_HOST: localhost + + PGUSER: postgres + PGPASSWORD: Password12! + PGSSLMODE: require + + # Add CWD to perl library path for PostgreSQL build on VS2019 + PERL5LIB: . + + # Select according to the service enabled + POSTGRES_DIR: C:\Program Files\PostgreSQL\9.6\ + + # The python used in the build process, not the one packages are built for + PYEXE: C:\Python36\python.exe + +matrix: + fast_finish: false + +services: + # Note: if you change this service also change POSTGRES_DIR + - postgresql96 + +cache: + # Rebuild cache if following file changes + # (See the file to zap the cache manually) + - C:\Others -> scripts\appveyor.cache_rebuild + +# Script called before repo cloning +# init: + +# Repository gets cloned, Cache is restored + +install: + - "%PYEXE% scripts\\appveyor.py install" + +# PostgreSQL server starts now + +build: off + +build_script: + - "%PYEXE% scripts\\appveyor.py build_script" + +after_build: + - "%PYEXE% scripts\\appveyor.py after_build" + +before_test: + - "%PYEXE% scripts\\appveyor.py before_test" + +test_script: + - "%PYEXE% scripts\\appveyor.py test_script" + +artifacts: + - path: dist\psycopg2-*\*.whl + name: wheel + - path: dist\psycopg2-*\*.exe + name: exe + + +# vim: set ts=4 sts=4 sw=4: diff --git a/.appveyor.yml b/.appveyor/tests.yml similarity index 91% rename from .appveyor.yml rename to .appveyor/tests.yml index d7edc0d7..e1b7c52b 100644 --- a/.appveyor.yml +++ b/.appveyor/tests.yml @@ -3,11 +3,6 @@ version : 2.x.{build} clone_folder: C:\Project environment: - global: - # MSVC Express 2008's setenv.cmd failes if /E:ON and /V:ON are not - # enabled in the batch script interpreter - CMD_IN_ENV: cmd /E:ON /V:ON /C .\appveyor\run_with_env.cmd - matrix: # For Python versions available on Appveyor, see # https://www.appveyor.com/docs/windows-images-software/#python @@ -20,6 +15,8 @@ environment: - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015, PY_VER: "36", PY_ARCH: "32"} - {APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015, PY_VER: "36", PY_ARCH: "64"} + WORKFLOW: tests + OPENSSL_VERSION: "1_1_1h" POSTGRES_VERSION: "13_0" diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index aa0f9c52..95d11ff3 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -50,3 +50,90 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 + + + build-manylinux_2_24: + strategy: + fail-fast: false + matrix: + include: + - platform: manylinux_2_24_x86_64 + - platform: manylinux_2_24_i686 + - platform: manylinux_2_24_aarch64 + - platform: manylinux_2_24_ppc64le + + runs-on: ubuntu-20.04 + steps: + - name: Checkout repos + uses: actions/checkout@v2 + + - name: Set up QEMU for multi-arch build + uses: docker/setup-qemu-action@v1 + + - name: Build packages + run: >- + docker run --rm + -e PLAT=${{ matrix.platform }} + -e PACKAGE_NAME=psycopg2-binary + -e PYVERS="cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310" + -e PSYCOPG2_TESTDB=postgres + -e PSYCOPG2_TESTDB_HOST=172.17.0.1 + -e PSYCOPG2_TESTDB_USER=postgres + -e PSYCOPG2_TESTDB_PASSWORD=password + -e PSYCOPG2_TEST_FAST=1 + -v `pwd`:/src + --workdir /src + quay.io/pypa/${{ matrix.platform }} + ./scripts/build/build_manylinux_2_24.sh + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: packages_${{ matrix.platform }} + path: | + dist/*/*${{ matrix.platform }}.whl + + services: + postgresql: + image: postgres:13 + env: + POSTGRES_PASSWORD: password + ports: + - 5432:5432 + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + + build-macos: + runs-on: macos-10.15 + strategy: + fail-fast: false + matrix: + python-version: ['3.6', '3.7', '3.8', '3.9'] + + steps: + - name: Checkout repos + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Build packages + run: ./scripts/build/build_macos.sh + env: + PACKAGE_NAME: psycopg2-binary + PSYCOPG2_TESTDB: postgres + PSYCOPG2_TEST_FAST: 1 + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: packages_macos + path: | + dist/*/*${{ matrix.platform }}.whl diff --git a/.gitignore b/.gitignore index 9a3bb038..457c9ab6 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ env? .vscode/ /rel /wheels +/packages diff --git a/NEWS b/NEWS index e2c7a62f..59679271 100644 --- a/NEWS +++ b/NEWS @@ -4,7 +4,6 @@ Current release What's new in psycopg 2.9 ------------------------- -- Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`). - ``with connection`` starts a transaction on autocommit transactions too (:ticket:`#941`). - Escape table and column names in `~cursor.copy_from()` and @@ -12,6 +11,14 @@ What's new in psycopg 2.9 - Connection exceptions with sqlstate ``08XXX`` reclassified as `~psycopg2.OperationalError` (a subclass of the previously used `~psycopg2.DatabaseError`) (:ticket:`#1148`). +- Include library dirs required from libpq to work around MacOS build problems + (:ticket:`#1200`). + +Other changes: + +- Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`). +- Build system for Linux/MacOS binary packages moved to GitHub action, now + providing :pep:`600`\-style wheels packages. What's new in psycopg 2.8.7 diff --git a/doc/release.rst b/doc/release.rst index 66c03a46..87a28759 100644 --- a/doc/release.rst +++ b/doc/release.rst @@ -15,24 +15,12 @@ How to make a psycopg2 release $ export VERSION=2.8.4 -- In the `Travis settings`__ you may want to be sure that the variables - ``TEST_PAST`` and ``TEST_FUTURE`` are set to 1 to check all - the supported postgres version. +- Push psycopg2 to master or to the maint branch. Make sure tests on `GitHub + Actions`__ and AppVeyor__ pass. -.. __: https://travis-ci.org/psycopg/psycopg2/settings - -- Push psycopg2 to master or to the maint branch. Make sure tests on Travis__ - and AppVeyor__ pass. - -.. __: https://travis-ci.org/psycopg/psycopg2 +.. __: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml .. __: https://ci.appveyor.com/project/psycopg/psycopg2 -- For an extra test merge or rebase the `test_i686`__ branch on the commit to - release and push it too: this will test with Python 32 bits and debug - versions. - -.. __: https://github.com/psycopg/psycopg2/tree/test_i686 - - Create a signed tag with the content of the relevant NEWS bit and push it. E.g.:: @@ -49,34 +37,31 @@ How to make a psycopg2 release - Fixed bug blah (:ticket:`#42`). ... -- Update the `psycopg2-wheels`_ submodule to the tag version and push. This - will build the packages on `Travis CI`__ and `AppVeyor`__ and upload them to - https://upload.psycopg.org/. +- Create the packages: -.. _psycopg2-wheels: https://github.com/psycopg/psycopg2-wheels -.. __: https://travis-ci.org/psycopg/psycopg2-wheels -.. __: https://ci.appveyor.com/project/psycopg/psycopg2-wheels + - On GitHub Actions run manually a `package build workflow`__. -- Download the packages generated (this assumes ssh configured properly):: + - On Appveyor change the `build settings`__ and replace the custom + configuration file name from ``.appveyor/tests.yml`` to + ``.appveyor/packages.yml`` (yeah, that sucks a bit. Remember to put it + back to testing). - $ rsync -arv psycopg-upload:psycopg2-${VERSION} . +.. __: https://github.com/psycopg/psycopg2/actions/workflows/packages.yml +.. __: https://ci.appveyor.com/project/psycopg/psycopg2/settings -- Sign the packages and upload the signatures back:: - - $ for f in psycopg2-${VERSION}/*.{exe,tar.gz,whl}; do \ - gpg --armor --detach-sign $f; - done - - $ rsync -arv psycopg2-${VERSION} psycopg-upload: +- When the workflows have finished download the packages using the + ``download_packages.py`` and ``download_packages_appveyor.py`` scripts from + the ``scripts/build`` directory. They will be saved in a + ``psycopg2-${VERSION}`` directory. - Remove the ``.exe`` from the dir, because we don't want to upload them on PyPI:: - $ rm -v psycopg2-${VERSION}/*.exe{,.asc} + $ rm -v psycopg2-${VERSION}/*.exe -- Only for stable packages: upload the packages and signatures on PyPI:: +- Only for stable packages: upload the signed packages on PyPI:: - $ twine upload psycopg2-${VERSION}/* + $ twine upload -s psycopg2-${VERSION}/* - Create a release and release notes in the psycopg website, announce to psycopg and pgsql-announce mailing lists. @@ -89,7 +74,7 @@ Releasing test packages Test packages may be uploaded on the `PyPI testing site`__ using:: - $ twine upload -r testpypi psycopg2-${VERSION}/* + $ twine upload -s -r testpypi psycopg2-${VERSION}/* assuming `proper configuration`__ of ``~/.pypirc``. diff --git a/scripts/appveyor.py b/scripts/appveyor.py index 428a6b48..39b3ebe2 100755 --- a/scripts/appveyor.py +++ b/scripts/appveyor.py @@ -699,13 +699,8 @@ class Options: @property def is_wheel(self): """Are we building the wheel packages or just the extension?""" - project_name = os.environ['APPVEYOR_PROJECT_NAME'] - if project_name == 'psycopg2': - return False - elif project_name == 'psycopg2-wheels': - return True - else: - raise Exception(f"unexpected project name: {project_name}") + workflow = os.environ["WORKFLOW"] + return workflow == "packages" @property def py_dir(self): @@ -801,12 +796,7 @@ class Options: @property def package_dir(self): - """ - The directory containing the psycopg code checkout dir. - - Building psycopg it is clone_dir, building the wheels it is a submodule. - """ - return self.clone_dir / 'psycopg2' if self.is_wheel else self.clone_dir + return self.clone_dir @property def dist_dir(self): diff --git a/scripts/build/build_macos.sh b/scripts/build/build_macos.sh new file mode 100755 index 00000000..4841ee75 --- /dev/null +++ b/scripts/build/build_macos.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Create macOS wheels for psycopg2 +# +# Following instructions from https://github.com/MacPython/wiki/wiki/Spinning-wheels +# Cargoculting pieces of implementation from https://github.com/matthew-brett/multibuild + +set -euo pipefail +set -x + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PRJDIR="$( cd "${DIR}/../.." && pwd )" + +brew install gnu-sed postgresql@13 + +# Start the database for testing +brew services start postgresql + +for i in $(seq 10 -1 0); do + eval pg_isready && break + if [ $i == 0 ]; then + echo "PostgreSQL service not ready, giving up" + exit 1 + fi + echo "PostgreSQL service not ready, waiting a bit, attempts left: $i" + sleep 5 +done + +# Find psycopg version +VERSION=$(grep -e ^PSYCOPG_VERSION "${PRJDIR}/setup.py" | gsed "s/.*'\(.*\)'/\1/") +# A gratuitous comment to fix broken vim syntax file: '") +DISTDIR="${PRJDIR}/dist/psycopg2-$VERSION" +mkdir -p "$DISTDIR" + +# Install required python packages +pip install -U pip wheel delocate + +# Replace the package name +gsed -i "s/^setup(name=\"psycopg2\"/setup(name=\"${PACKAGE_NAME}\"/" \ + "${PRJDIR}/setup.py" + +# Build the wheels +WHEELDIR="${PRJDIR}/wheels" +pip wheel -w ${WHEELDIR} . +delocate-listdeps ${WHEELDIR}/*.whl + +# Check where is the libpq. I'm gonna kill it for testing +if [[ -z "${LIBPQ:-}" ]]; then + export LIBPQ=$(delocate-listdeps ${WHEELDIR}/*.whl | grep libpq) +fi + +delocate-wheel ${WHEELDIR}/*.whl +# https://github.com/MacPython/wiki/wiki/Spinning-wheels#question-will-pip-give-me-a-broken-wheel +delocate-addplat --rm-orig -x 10_9 -x 10_10 ${WHEELDIR}/*.whl +cp ${WHEELDIR}/*.whl ${DISTDIR} + +# kill the libpq to make sure tests don't depend on it +mv "$LIBPQ" "${LIBPQ}-bye" + +# Install and test the built wheel +pip install ${PACKAGE_NAME} --no-index -f "$DISTDIR" + +# Print psycopg and libpq versions +python -c "import psycopg2; print(psycopg2.__version__)" +python -c "import psycopg2; print(psycopg2.__libpq_version__)" +python -c "import psycopg2; print(psycopg2.extensions.libpq_version())" + +# fail if we are not using the expected libpq library +# Disabled as we just use what's available on the system on macOS +# if [[ "${WANT_LIBPQ:-}" ]]; then +# python -c "import psycopg2, sys; sys.exit(${WANT_LIBPQ} != psycopg2.extensions.libpq_version())" +# fi + +python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')" + +# just because I'm a boy scout +mv "${LIBPQ}-bye" "$LIBPQ" diff --git a/scripts/build/build_manylinux_2_24.sh b/scripts/build/build_manylinux_2_24.sh new file mode 100755 index 00000000..2d247da8 --- /dev/null +++ b/scripts/build/build_manylinux_2_24.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +# Create manylinux_2_24 wheels for psycopg2 +# +# Look at the .github/workflows/packages.yml file for hints about how to use it. + +set -euo pipefail +set -x + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PRJDIR="$( cd "${DIR}/../.." && pwd )" + +# Build all the available versions, or just the ones specified in PYVERS +if [ ! "${PYVERS:-}" ]; then + PYVERS="$(ls /opt/python/)" +fi + +# Find psycopg version +VERSION=$(grep -e ^PSYCOPG_VERSION "${PRJDIR}/setup.py" | sed "s/.*'\(.*\)'/\1/") +# A gratuitous comment to fix broken vim syntax file: '") +DISTDIR="${PRJDIR}/dist/psycopg2-$VERSION" + +# Replace the package name +if [[ "${PACKAGE_NAME:-}" ]]; then + sed -i "s/^setup(name=\"psycopg2\"/setup(name=\"${PACKAGE_NAME}\"/" \ + "${PRJDIR}/setup.py" +fi + +# Install prerequisite libraries +curl -s https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - +echo "deb http://apt.postgresql.org/pub/repos/apt stretch-pgdg main" \ + > /etc/apt/sources.list.d/pgdg.list +apt-get -y update +apt-get install -y libpq-dev + +# Create the wheel packages +for PYVER in $PYVERS; do + PYBIN="/opt/python/${PYVER}/bin" + "${PYBIN}/pip" wheel "${PRJDIR}" -w "${PRJDIR}/dist/" +done + +# Bundle external shared libraries into the wheels +for WHL in "${PRJDIR}"/dist/*.whl; do + auditwheel repair "$WHL" -w "$DISTDIR" +done + +# Make sure the libpq is not in the system +for f in $(find /usr/lib /usr/lib64 -name libpq\*) ; do + mkdir -pv "/libpqbak/$(dirname $f)" + mv -v "$f" "/libpqbak/$(dirname $f)" +done + +# Install packages and test +cd "${PRJDIR}" +for PYVER in $PYVERS; do + PYBIN="/opt/python/${PYVER}/bin" + "${PYBIN}/pip" install ${PACKAGE_NAME} --no-index -f "$DISTDIR" + + # Print psycopg and libpq versions + "${PYBIN}/python" -c "import psycopg2; print(psycopg2.__version__)" + "${PYBIN}/python" -c "import psycopg2; print(psycopg2.__libpq_version__)" + "${PYBIN}/python" -c "import psycopg2; print(psycopg2.extensions.libpq_version())" + + # Fail if we are not using the expected libpq library + if [[ "${WANT_LIBPQ:-}" ]]; then + "${PYBIN}/python" -c "import psycopg2, sys; sys.exit(${WANT_LIBPQ} != psycopg2.extensions.libpq_version())" + fi + + "${PYBIN}/python" -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')" +done + +# Restore the libpq packages +for f in $(cd /libpqbak/ && find . -not -type d); do + mv -v "/libpqbak/$f" "/$f" +done diff --git a/scripts/build/download_packages.py b/scripts/build/download_packages.py new file mode 100755 index 00000000..46ce149c --- /dev/null +++ b/scripts/build/download_packages.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +"""Download packages from github actions artifacts +""" + +import io +import os +import sys +import logging +from pathlib import Path +from zipfile import ZipFile + +import requests + +logger = logging.getLogger() +logging.basicConfig( + level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s" +) + +REPOS = "psycopg/psycopg2" +WORKFLOW_NAME = "Build packages" + + +class ScriptError(Exception): + """Controlled exception raised by the script.""" + + +def main(): + try: + token = os.environ["GITHUB_TOKEN"] + except KeyError: + raise ScriptError("please set a GITHUB_TOKEN to download artifacts") + + s = requests.Session() + s.headers["Accept"] = "application/vnd.github.v3+json" + s.headers["Authorization"] = f"token {token}" + + logger.info("looking for recent runs") + resp = s.get(f"https://api.github.com/repos/{REPOS}/actions/runs?per_page=10") + resp.raise_for_status() + for run in resp.json()["workflow_runs"]: + if run["name"] == WORKFLOW_NAME: + break + else: + raise ScriptError(f"couldn't find {WORKFLOW_NAME!r} in recent runs") + + logger.info(f"looking for run {run['id']} artifacts") + resp = s.get(f"{run['url']}/artifacts") + resp.raise_for_status() + artifacts = resp.json()["artifacts"] + + dest = Path("packages") + if not dest.exists(): + logger.info(f"creating dir {dest}") + dest.mkdir() + + for artifact in artifacts: + logger.info(f"downloading {artifact['name']} archive") + zip_url = artifact["archive_download_url"] + resp = s.get(zip_url) + with ZipFile(io.BytesIO(resp.content)) as zf: + logger.info("extracting archive content") + zf.extractall(dest) + + logger.info(f"now you can run: 'twine upload -s {dest}/*'") + + +if __name__ == "__main__": + try: + sys.exit(main()) + + except ScriptError as e: + logger.error("%s", e) + sys.exit(1) + + except KeyboardInterrupt: + logger.info("user interrupt") + sys.exit(1) diff --git a/scripts/build/download_packages_appveyor.py b/scripts/build/download_packages_appveyor.py new file mode 100755 index 00000000..7ac55b41 --- /dev/null +++ b/scripts/build/download_packages_appveyor.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +"""Download packages from github actions artifacts +""" + +import os +import sys +import logging +from pathlib import Path + +import requests + +logger = logging.getLogger() +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") + +API_URL = "https://ci.appveyor.com/api" +REPOS = "psycopg/psycopg2" +WORKFLOW_NAME = "Build packages" + + +class ScriptError(Exception): + """Controlled exception raised by the script.""" + + +def main(): + try: + token = os.environ["APPVEYOR_TOKEN"] + except KeyError: + raise ScriptError("please set a APPVEYOR_TOKEN to download artifacts") + + s = requests.Session() + s.headers["Content-Type"] = "application/json" + s.headers["Authorization"] = f"Bearer {token}" + + logger.info("fetching last run") + resp = s.get(f"{API_URL}/projects/{REPOS}/") + resp.raise_for_status() + data = resp.json() + jobs = data["build"]["jobs"] + for job in jobs: + if job["status"] != "success": + raise ScriptError("status for job {job['jobId']} is {job['status']}") + + logger.info(f"fetching artifacts info for {job['name']}") + resp = s.get(f"{API_URL}/buildjobs/{job['jobId']}/artifacts/") + resp.raise_for_status() + afs = resp.json() + for af in afs: + fn = af["fileName"] + if fn.startswith("dist/"): + fn = fn.split("/", 1)[1] + dest = Path("packages") / fn + logger.info(f"downloading {dest}") + resp = s.get( + f"{API_URL}/buildjobs/{job['jobId']}/artifacts/{af['fileName']}" + ) + resp.raise_for_status() + if not dest.parent.exists(): + dest.parent.mkdir() + + with dest.open("wb") as f: + f.write(resp.content) + + logger.info("now you can run: 'twine upload -s packages/*'") + + +if __name__ == "__main__": + try: + sys.exit(main()) + + except ScriptError as e: + logger.error("%s", e) + sys.exit(1) + + except KeyboardInterrupt: + logger.info("user interrupt") + sys.exit(1) + diff --git a/setup.py b/setup.py index a563fd18..2cdb7c2c 100644 --- a/setup.py +++ b/setup.py @@ -376,6 +376,16 @@ For further information please check the 'doc/src/install.rst' file (also at self.library_dirs.append(pg_config_helper.query("libdir")) self.include_dirs.append(pg_config_helper.query("includedir")) self.include_dirs.append(pg_config_helper.query("includedir-server")) + + # add includedirs from cppflags, libdirs from ldflags + for token in pg_config_helper.query("ldflags").split(): + if token.startswith("-L"): + self.library_dirs.append(token[2:]) + + for token in pg_config_helper.query("cppflags").split(): + if token.startswith("-I"): + self.include_dirs.append(token[2:]) + pgversion = pg_config_helper.query("version").split()[1] verre = re.compile( diff --git a/tests/testutils.py b/tests/testutils.py index f0c32645..3f8ceb3a 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -178,7 +178,10 @@ class ConnectingTestCase(unittest.TestCase): if libname is None and platform.system() == 'Windows': raise self.skipTest("can't import libpq on windows") - rv = ConnectingTestCase._libpq = ctypes.pydll.LoadLibrary(libname) + try: + rv = ConnectingTestCase._libpq = ctypes.pydll.LoadLibrary(libname) + except OSError as e: + raise self.skipTest("couldn't open libpq for testing: %s" % e) return rv