]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Add tests for the Shapely adapter based on PostGIS
authorJacopo Farina <jacopo.farina@flixbus.com>
Wed, 15 Sep 2021 13:38:25 +0000 (15:38 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Tue, 21 Sep 2021 18:02:18 +0000 (19:02 +0100)
.github/workflows/tests.yml
psycopg/tox.ini
tests/conftest.py
tests/test_shapely.py [new file with mode: 0644]

index aabbf400522e5c655727d4880a73952ee995d86c..c79dcc320196878a0fa43b35d74638653ef55919 100644 (file)
@@ -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
index e6fdacdabf8295a480d928836d558f367c0a8213..6ec9d605a2e3409c88210dca35ccb6abff816675 100644 (file)
@@ -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
index 32181ceebb42899152033551fe7e229cf29eacf5..561695317c6c79fdc316bbd603b21b9b8d0abaef 100644 (file)
@@ -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 (file)
index 0000000..09fc788
--- /dev/null
@@ -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[:]