Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "expose primary keys" option to create_postgis_store #25

Merged
merged 15 commits into from
Sep 24, 2024
Merged
18 changes: 10 additions & 8 deletions .github/workflows/e2e_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ jobs:
fail-fast: false
matrix:
py:
- "3.10"
- "3.12"
postgis:
- "9.6-2.5-alpine"
- "10-2.5-alpine"
- "11-2.5-alpine"
- "12-2.5-alpine"
# Not working on PostGIS 3.2 yet
# - "9.6-3.2-alpine"
# - "10-3.2-alpine"
# - "11-3.2-alpine"
# - "12-3.2-alpine"
# Not working on PostGIS 3.4 yet
# - "12-3.4-alpine"
# - "13-3.4-alpine"
# - "14-3.4-alpine"
# - "15-3.4-alpine"
# - "16-3.4-alpine"
services:
geoserver:
image: tethysplatform/geoserver
Expand All @@ -52,9 +53,9 @@ jobs:
ports:
- 5432:5432
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.py }} for test
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.py }}
- name: Install dependencies
Expand All @@ -66,6 +67,7 @@ jobs:
run: |
PGPASSWORD=mysecretpassword psql -U postgres -h localhost -c "CREATE DATABASE tds_tests WITH OWNER postgres;"
PGPASSWORD=mysecretpassword psql -U postgres -h localhost -d tds_tests -c "CREATE EXTENSION postgis;"
if [[ ${{ matrix.postgis }} == *"3.4-alpine"* ]]; then PGPASSWORD=mysecretpassword psql -U postgres -h localhost -d tds_tests -c "CREATE EXTENSION postgis_raster;"; fi
sleep 10
- name: Run tests
run: tox -e e2e_gs_tests
10 changes: 5 additions & 5 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ jobs:
- windows
- macos
py:
# - "3.13"
- "3.12"
- "3.11"
- "3.10"
- "3.9"
- "3.8"
- "3.7"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.py }} for test
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.py }}
- name: Install dependencies
Expand All @@ -38,7 +38,7 @@ jobs:
- name: Run tests
run: tox
- name: Coveralls
if: matrix.os == 'ubuntu' && matrix.py == 3.10
if: matrix.os == 'ubuntu' && matrix.py == 3.12
run: coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 changes: 9 additions & 3 deletions tests/e2e_tests/geoserver_engine_e2e_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ def setup_postgis_table(self):
insert_sql = "INSERT INTO {table} VALUES ({id}, '{name}', ST_GeomFromText('POINT({lon} {lat})', 4326));"
rows = [
{"id": 1, "name": "Aquaveo", "lat": 40.276039, "lon": -111.651120},
{"id": 2, "name": "BYU", "lat": 40.252335, "lon": -111.649326},
{"id": 2, "name": "Lynker", "lat": 39.111534, "lon": -77.556859},
{"id": 3, "name": "CHL", "lat": 32.299343, "lon": -90.866044},
]

for r in rows:
Expand Down Expand Up @@ -989,10 +990,12 @@ def test_link_and_add_table(self):
# Execute
response = self.geoserver_engine.create_layer_from_postgis_store(
store_id=store_id,
table=self.pg_table_name
table=self.pg_table_name,
debug=True
)

# Check for success response
# TODO: returns an error in PostGIS 3.4: Internal Server Error(500): :java.io.IOException: Error occured calculating bounds for points
self.assertTrue(response['success'])

# TEST list_stores
Expand Down Expand Up @@ -1136,8 +1139,11 @@ def test_create_sql_view_layer(self):
# Create layer from postgis store
response = self.geoserver_engine.create_layer_from_postgis_store(
store_id=store_id,
table=self.pg_table_name
table=self.pg_table_name,
debug=True
)

# TODO: returns an error in PostGIS 3.4: Internal Server Error(500): :java.io.IOException: Error occured calculating bounds for points
self.assertTrue(response['success'])

# Pause to let GeoServer catch up before continuing
Expand Down
61 changes: 57 additions & 4 deletions tests/unit_tests/test_geoserver_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def test_list_resources(self, mock_catalog):
for n in self.resource_names:
self.assertIn(n, result)

mc.get_resources.called_with(store=None, workspace=None)
mc.get_resources.assert_called_with(stores=None, workspaces=None)

@mock.patch('tethys_dataset_services.engines.geoserver_engine.GeoServerCatalog')
def test_list_resources_with_properties(self, mock_catalog):
Expand Down Expand Up @@ -244,7 +244,7 @@ def test_list_resources_with_properties(self, mock_catalog):
self.assertIn('store', r)
self.assertEqual(self.store_name, r['store'])

mc.get_resources.called_with(store=None, workspace=None)
mc.get_resources.assert_called_with(stores=None, workspaces=None)

@mock.patch('tethys_dataset_services.engines.geoserver_engine.GeoServerCatalog')
def test_list_resources_ambiguous_error(self, mock_catalog):
Expand All @@ -260,7 +260,7 @@ def test_list_resources_ambiguous_error(self, mock_catalog):
# Success
self.assertFalse(response['success'])

mc.get_resources.called_with(store=None, workspace=None)
mc.get_resources.assert_called_with(stores=None, workspaces=None)

@mock.patch('tethys_dataset_services.engines.geoserver_engine.GeoServerCatalog')
def test_list_resources_multiple_stores_error(self, mock_catalog):
Expand All @@ -277,7 +277,7 @@ def test_list_resources_multiple_stores_error(self, mock_catalog):
self.assertFalse(response['success'])
self.assertIn('Multiple stores found named', response['error'])

mc.get_resources.called_with(store=None, workspace=None)
mc.get_resources.assert_called_with(stores=None, workspaces=None)

@mock.patch('tethys_dataset_services.engines.geoserver_engine.GeoServerCatalog')
def test_list_layers(self, mock_catalog):
Expand Down Expand Up @@ -3708,6 +3708,7 @@ def test_create_postgis_store_validate_connection(self, mock_post, _):
<entry key="Max connection idle time">{7}</entry>
<entry key="Evictor run periodicity">{8}</entry>
<entry key="validate connections">true</entry>
<entry key="Expose primary keys">false</entry>
</connectionParameters>
</dataStore>
""".format('foo', host, port, database, username, password, max_connections, max_connection_idle_time,
Expand Down Expand Up @@ -3756,6 +3757,7 @@ def test_create_postgis_store_validate_connection_false(self, mock_workspace, mo
<entry key="Max connection idle time">{7}</entry>
<entry key="Evictor run periodicity">{8}</entry>
<entry key="validate connections">false</entry>
<entry key="Expose primary keys">false</entry>
</connectionParameters>
</dataStore>
""".format('foo', host, port, database, username, password, max_connections, max_connection_idle_time,
Expand All @@ -3774,6 +3776,56 @@ def test_create_postgis_store_validate_connection_false(self, mock_workspace, mo
max_connection_idle_time, evictor_run_periodicity, validate_connections=False)
mock_post.assert_called_with(url=rest_endpoint, data=xml, headers=expected_headers, auth=self.auth)

@mock.patch('tethys_dataset_services.engines.geoserver_engine.GeoServerSpatialDatasetEngine.get_store')
@mock.patch('tethys_dataset_services.engines.geoserver_engine.requests.post')
@mock.patch('tethys_dataset_services.engines.geoserver_engine.GeoServerCatalog.get_default_workspace')
def test_create_postgis_store_expose_primary_keys_true(self, mock_workspace, mock_post, _):
mock_post.return_value = MockResponse(201)
store_id = 'foo'
mock_workspace().name = self.workspace_name
host = 'localhost'
port = '5432'
database = 'foo_db'
username = 'user'
password = 'pass'
max_connections = 10
max_connection_idle_time = 40
evictor_run_periodicity = 60

xml = """
<dataStore>
<name>{0}</name>
<connectionParameters>
<entry key="host">{1}</entry>
<entry key="port">{2}</entry>
<entry key="database">{3}</entry>
<entry key="user">{4}</entry>
<entry key="passwd">{5}</entry>
<entry key="dbtype">postgis</entry>
<entry key="max connections">{6}</entry>
<entry key="Max connection idle time">{7}</entry>
<entry key="Evictor run periodicity">{8}</entry>
<entry key="validate connections">false</entry>
<entry key="Expose primary keys">true</entry>
</connectionParameters>
</dataStore>
""".format('foo', host, port, database, username, password, max_connections, max_connection_idle_time,
evictor_run_periodicity)

expected_headers = {
"Content-type": "text/xml",
"Accept": "application/xml"
}

rest_endpoint = '{endpoint}workspaces/{workspace}/datastores'.format(
endpoint=self.endpoint,
workspace=self.workspace_name
)
self.engine.create_postgis_store(store_id, host, port, database, username, password, max_connections,
max_connection_idle_time, evictor_run_periodicity, validate_connections=False,
expose_primary_keys=True)
mock_post.assert_called_with(url=rest_endpoint, data=xml, headers=expected_headers, auth=self.auth)

@mock.patch('tethys_dataset_services.engines.geoserver_engine.GeoServerSpatialDatasetEngine.get_store')
@mock.patch('tethys_dataset_services.engines.geoserver_engine.log')
@mock.patch('tethys_dataset_services.engines.geoserver_engine.requests.post')
Expand Down Expand Up @@ -3803,6 +3855,7 @@ def test_create_postgis_store_not_201(self, mock_post, mock_logger, _):
<entry key="Max connection idle time">{7}</entry>
<entry key="Evictor run periodicity">{8}</entry>
<entry key="validate connections">true</entry>
<entry key="Expose primary keys">false</entry>
</connectionParameters>
</dataStore>
""".format('foo', host, port, database, username, password, max_connections, max_connection_idle_time,
Expand Down
29 changes: 15 additions & 14 deletions tethys_dataset_services/engines/geoserver_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -1250,7 +1250,7 @@ def link_sqlalchemy_db_to_geoserver(self, store_id, sqlalchemy_engine, max_conne

def create_postgis_store(self, store_id, host, port, database, username, password, max_connections=5,
max_connection_idle_time=30, evictor_run_periodicity=30, validate_connections=True,
debug=False):
expose_primary_keys=False, debug=False):
"""
Use this method to link an existing PostGIS database to GeoServer as a feature store. Note that this method only works for data in vector formats.

Expand All @@ -1265,6 +1265,7 @@ def create_postgis_store(self, store_id, host, port, database, username, passwor
max_connection_idle_time (int, optional): Number of seconds a connections can stay idle before the evictor considers closing it. Defaults to 30 seconds.
evictor_run_periodicity (int, optional): Number of seconds between idle connection evictor runs. Defaults to 30 seconds.
validate_connections (bool, optional): Test connections before using. Defaults to True.
expose_primary_keys (bool, optional):
debug (bool, optional): Pretty print the response dictionary to the console for debugging. Defaults to False.

Returns:
Expand All @@ -1283,24 +1284,24 @@ def create_postgis_store(self, store_id, host, port, database, username, passwor
workspace = self.catalog.get_default_workspace().name

# Create the store
xml = """
xml = f"""
<dataStore>
<name>{0}</name>
<name>{name}</name>
<connectionParameters>
<entry key="host">{1}</entry>
<entry key="port">{2}</entry>
<entry key="database">{3}</entry>
<entry key="user">{4}</entry>
<entry key="passwd">{5}</entry>
<entry key="host">{host}</entry>
<entry key="port">{port}</entry>
<entry key="database">{database}</entry>
<entry key="user">{username}</entry>
<entry key="passwd">{password}</entry>
<entry key="dbtype">postgis</entry>
<entry key="max connections">{6}</entry>
<entry key="Max connection idle time">{7}</entry>
<entry key="Evictor run periodicity">{8}</entry>
<entry key="validate connections">{9}</entry>
<entry key="max connections">{max_connections}</entry>
<entry key="Max connection idle time">{max_connection_idle_time}</entry>
<entry key="Evictor run periodicity">{evictor_run_periodicity}</entry>
<entry key="validate connections">{str(validate_connections).lower()}</entry>
<entry key="Expose primary keys">{str(expose_primary_keys).lower()}</entry>
</connectionParameters>
</dataStore>
""".format(name, host, port, database, username, password, max_connections, max_connection_idle_time,
evictor_run_periodicity, str(validate_connections).lower())
"""

# Prepare headers
headers = {
Expand Down
6 changes: 3 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[tox]
isolated_build = True
envlist = py37, py38, py39, py310, py311, flake8, clean
envlist = py39, py310, py311, py312, py313, flake8, clean

[gh-actions]
python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310
3.11: py311
3.12: py312
3.13: py313

[testenv]
deps =
Expand Down