From: Jacopo Farina Date: Wed, 15 Sep 2021 13:38:25 +0000 (+0200) Subject: Add tests for the Shapely adapter based on PostGIS X-Git-Tag: 3.0~71^2~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=909f2c72bba0da75f5404324b138984f64eef57c;p=thirdparty%2Fpsycopg.git Add tests for the Shapely adapter based on PostGIS --- diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aabbf4005..c79dcc320 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,15 +13,16 @@ jobs: fail-fast: false matrix: include: - - {impl: python, python: 3.6, postgres: 10} - - {impl: python, python: 3.7, postgres: 11} - - {impl: python, python: 3.8, postgres: 12} - - {impl: python, python: 3.9, postgres: 13} - - {impl: c, python: 3.6, postgres: 13} - - {impl: c, python: 3.7, postgres: 12} - - {impl: c, python: 3.8, postgres: 11} - - {impl: c, python: 3.9, postgres: 10} - - {impl: dns, python: 3.9, postgres: 13} + - {impl: python, python: 3.6, image: "postgres:10"} + - {impl: python, python: 3.7, image: "postgres:11"} + - {impl: python, python: 3.8, image: "postgres:12"} + - {impl: python, python: 3.9, image: "postgres:13"} + - {impl: c, python: 3.6, image: "postgres:13"} + - {impl: c, python: 3.7, image: "postgres:12"} + - {impl: c, python: 3.8, image: "postgres:11"} + - {impl: c, python: 3.9, image: "postgres:10"} + - {impl: dns, python: 3.9, image: "postgres:13"} + - {impl: postgis, python: 3.9, image: "postgis/postgis:13-master"} env: PSYCOPG_IMPL: ${{ matrix.impl }} @@ -56,9 +57,15 @@ jobs: env: PSYCOPG_IMPL: python + - name: Run PostGIS-related tests + run: tox -c psycopg -e postgis -- --color yes -m postgis + if: ${{ matrix.impl == 'postgis' }} + env: + PSYCOPG_IMPL: python + services: postgresql: - image: postgres:${{ matrix.postgres }} + image: ${{ matrix.image }} env: POSTGRES_PASSWORD: password ports: @@ -120,7 +127,6 @@ jobs: run: tox -c psycopg_c -e ${{ matrix.python }} -- --color yes if: ${{ matrix.impl == 'c' }} - windows: name: Test on Windows runs-on: windows-2019 diff --git a/psycopg/tox.ini b/psycopg/tox.ini index e6fdacdab..6ec9d605a 100644 --- a/psycopg/tox.ini +++ b/psycopg/tox.ini @@ -20,6 +20,15 @@ extras = test deps = dnspython +[testenv:postgis] +changedir = .. +commands = + pytest {posargs} +passenv = PG* PSYCOPG_TEST_DSN PYTEST_ADDOPTS PSYCOPG_IMPL +extras = test +deps = + shapely + [flake8] max-line-length = 85 ignore = W503, E203 diff --git a/tests/conftest.py b/tests/conftest.py index 32181ceeb..561695317 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,11 @@ def pytest_configure(config): "dns: the test requires dnspython to run", ) + config.addinivalue_line( + "markers", + "postgis: the test requires the PostGIS extension to run", + ) + def pytest_addoption(parser): parser.addoption( diff --git a/tests/test_shapely.py b/tests/test_shapely.py new file mode 100644 index 000000000..09fc7880c --- /dev/null +++ b/tests/test_shapely.py @@ -0,0 +1,127 @@ +import pytest + +from psycopg.pq import Format +from psycopg.types import TypeInfo +from psycopg.types.shapely import register_shapely +from psycopg import ProgrammingError +from shapely.geometry import Point, Polygon + +pytestmark = [pytest.mark.postgis] +pytest.importorskip("shapely") + +# real example, with CRS and "holes" +MULTIPOLYGON_GEOJSON = """ +{ + "type":"MultiPolygon", + "crs":{ + "type":"name", + "properties":{ + "name":"EPSG:3857" + } + }, + "coordinates":[ + [ + [ + [1489574.61111389, 6894228.638802719], + [89576.815239808, 6894208.60747024], + [89576.904295401, 6894207.820852726], + [89577.99522641, 6894208.022080451], + [89577.961830563, 6894209.229446936], + [89589.227363031, 6894210.601454523], + [89594.615226386, 6894161.849595264], + [89600.314784314, 6894111.37846976], + [89651.187791607, 6894116.774968589], + [89648.49385993, 6894140.226914071], + [89642.92788539, 6894193.423936413], + [89639.721884055, 6894224.08372821], + [89589.283022777, 6894218.431048969], + [89588.192091767, 6894230.248628867], + [89574.61111389, 6894228.638802719] + ], + [ + [1489610.344670435, 6894182.466199101], + [89625.985058891, 6894184.258949757], + [89629.547282597, 6894153.270030369], + [89613.918026089, 6894151.458993318], + [89610.344670435, 6894182.466199101] + ] + ] + ] +}""" + +SAMPLE_POINT_GEOJSON = '{"type":"Point","coordinates":[1.2, 3.4]}' + +SAMPLE_POINT = Point(1.2, 3.4) +SAMPLE_POLYGON = Polygon([(0, 0), (1, 1), (1, 0)]) + + +@pytest.fixture +def shapely_conn(conn): + info = TypeInfo.fetch(conn, "geometry") + register_shapely(info, conn) + return conn + + +def test_no_adapter(conn): + point = Point(1.2, 3.4) + with pytest.raises(ProgrammingError, match="cannot adapt type Point"): + conn.execute("SELECT pg_typeof(%s)", [point]).fetchone()[0] + + +def test_with_adapter(shapely_conn): + assert ( + shapely_conn.execute( + "SELECT pg_typeof(%s)", + [SAMPLE_POINT], + ).fetchone()[0] + == "geometry" + ) + + assert ( + shapely_conn.execute( + "SELECT pg_typeof(%s)", + [SAMPLE_POLYGON], + ).fetchone()[0] + == "geometry" + ) + + +@pytest.mark.parametrize("fmt_out", [Format.TEXT, Format.BINARY]) +def test_write_read_shape(shapely_conn, fmt_out): + with shapely_conn.cursor(binary=fmt_out) as cur: + cur.execute( + """ + create table sample_geoms( + id INTEGER PRIMARY KEY, + geom geometry + ) + """ + ) + cur.execute( + "insert into sample_geoms(id, geom) VALUES(1, %s)", (SAMPLE_POINT,) + ) + cur.execute( + "insert into sample_geoms(id, geom) VALUES(2, %s)", + (SAMPLE_POLYGON,), + ) + + cur.execute("select geom from sample_geoms where id=1") + result = cur.fetchone()[0] + assert result == SAMPLE_POINT + + cur.execute("select geom from sample_geoms where id=2") + result = cur.fetchone()[0] + assert result == SAMPLE_POLYGON + + +@pytest.mark.parametrize("fmt_out", [Format.TEXT, Format.BINARY]) +def test_match_geojson(shapely_conn, fmt_out): + with shapely_conn.cursor(binary=fmt_out) as cur: + cur.execute( + f""" + select ST_GeomFromGeoJSON('{SAMPLE_POINT_GEOJSON}') + """ + ) + result = cur.fetchone()[0] + # clone the coordinates to have a list instead of a shapely wrapper + assert result.coords[:] == SAMPLE_POINT.coords[:]