From f7279dc2573b0ca2640d283c8241e74675be23ca Mon Sep 17 00:00:00 2001 From: scranford1 Date: Fri, 14 Aug 2026 15:18:31 -0400 Subject: [PATCH 1/3] Resolve latest sonarqube findings (#44) * Resolve latest sonarqube findings --- .devcontainer/dev_container.dockerfile | 14 +++++--------- .devcontainer/devcontainer.json | 4 ++-- example/app.py | 4 ++-- ogc/edr/edr_api.py | 6 +++--- ogc/edr/test/conftest.py | 10 +++++----- ogc/edr/test/test_edr_routes.py | 3 ++- ogc/test/test_core.py | 8 ++++---- ogc/test/test_input_security.py | 2 +- ogc/test/test_input_validation.py | 18 +++++++++--------- ogc/test/test_servers.py | 8 ++++---- ogc/wcs_response_1_0_0.py | 2 +- ogc/wms_request_1_3_0.py | 2 +- ogc/wms_response_1_3_0.py | 6 +++--- ogc/wmts/test/conftest.py | 4 ++-- ogc/wmts/test/test_wmts_routes.py | 6 ++++-- 15 files changed, 48 insertions(+), 49 deletions(-) diff --git a/.devcontainer/dev_container.dockerfile b/.devcontainer/dev_container.dockerfile index f602887..c3b115b 100644 --- a/.devcontainer/dev_container.dockerfile +++ b/.devcontainer/dev_container.dockerfile @@ -5,21 +5,16 @@ FROM ${BASE_URL} USER root # We'll be running as a non-root user in a container and may want root permissions -RUN apt update && apt -y install nano ssh sudo && apt clean - -# Install setup tools and dependencies -WORKDIR /app -COPY . /app -RUN pip install --upgrade pip setuptools && pip install .[dev] +RUN apt update && apt -y install --no-install-recommends nano ssh sudo && apt clean # Set up user to match the host OS (https://stackoverflow.com/a/78621662/415551) ARG HOST_USER ARG HOST_UID ARG HOST_GID -RUN addgroup --gid ${HOST_GID} ${HOST_USER} \ - && adduser --gecos "" --disabled-password --uid ${HOST_UID} --gid ${HOST_GID} ${HOST_USER} \ - && usermod -aG sudo ${HOST_USER} \ +RUN addgroup --gid "${HOST_GID}" "${HOST_USER}" \ + && adduser --gecos "" --disabled-password --uid "${HOST_UID}" --gid "${HOST_GID}" "${HOST_USER}" \ + && usermod -aG sudo "${HOST_USER}" \ && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers ENV HOME /home/${HOST_USER} @@ -27,3 +22,4 @@ ENV TMPDIR=/tmp WORKDIR /home/${HOST_USER} USER ${HOST_USER} +ENV PATH "/home/${HOST_USER}/.local/bin:$PATH" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9c1d714..fb8fddb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -39,11 +39,11 @@ ] } } - } + }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Uncomment the next line to run commands after the container is created. - // "postCreateCommand": "cat /etc/os-release", + "postCreateCommand": "pip install --upgrade pip setuptools && pip install --no-cache-dir -e .[dev]" // Configure tool-specific properties. // "customizations": {}, // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. diff --git a/example/app.py b/example/app.py index a8e7424..10a285f 100755 --- a/example/app.py +++ b/example/app.py @@ -110,13 +110,13 @@ def api_home(endpoint): # add in some other endpoints. -@app.route("/") +@app.route("/", methods=["GET"]) def home(): return """This is an example OGC flask app. See FULL and PARTIAL endpoints.""" -@app.route("/layers/") +@app.route("/layers/", methods=["GET"]) def check_layers(layer): match_object = re.match("[a-zA-Z0-9]+", layer) diff --git a/ogc/edr/edr_api.py b/ogc/edr/edr_api.py index b385a64..cb5cc80 100644 --- a/ogc/edr/edr_api.py +++ b/ogc/edr/edr_api.py @@ -7,7 +7,7 @@ from functools import wraps from http import HTTPStatus from datetime import datetime, timezone -from typing import Tuple, List, Dict, Any, Union, Callable +from typing import Tuple, List, Dict, Any, Callable from traitlets import TraitError from ogc import podpac as pogc @@ -328,12 +328,12 @@ def get_collection_edr_query( return pygeoedr.get_collection_edr_query(api, request, dataset, instance, query_type, location_id) @staticmethod - def _temporal_extents(times: List[Union[np.datetime64, datetime]], trs: str | None) -> Dict[str, Any]: + def _temporal_extents(times: List[np.datetime64 | datetime], trs: str | None) -> Dict[str, Any]: """Get the temporal extents for the provided times and reference system. Parameters ---------- - times : List[Union[np.datetime64, datetime]] + times : List[np.datetime64 | datetime] Times used to create the temporal extent. trs : str | None The reference system for the times. diff --git a/ogc/edr/test/conftest.py b/ogc/edr/test/conftest.py index 6da1229..408bf1a 100644 --- a/ogc/edr/test/conftest.py +++ b/ogc/edr/test/conftest.py @@ -62,7 +62,7 @@ def set_env_vars(): importlib.reload(settings) -@pytest.fixture() +@pytest.fixture def layers() -> List[pogc.Layer]: """List of test layers. @@ -74,7 +74,7 @@ def layers() -> List[pogc.Layer]: return [layer1, layer2] -@pytest.fixture() +@pytest.fixture def layers_no_instance() -> List[pogc.Layer]: """List of test layers without instances. @@ -86,7 +86,7 @@ def layers_no_instance() -> List[pogc.Layer]: return [layer3] -@pytest.fixture() +@pytest.fixture def single_layer_cube_args() -> Dict[str, Any]: """Dictionary of valid request arguments that align to a single test layer cube request. @@ -104,7 +104,7 @@ def single_layer_cube_args() -> Dict[str, Any]: } -@pytest.fixture() +@pytest.fixture def single_layer_cube_args_internal() -> Dict[str, Any]: """Dictionary of valid arguments that align to a single test layer request with internal pygeoapi keys. @@ -123,7 +123,7 @@ def single_layer_cube_args_internal() -> Dict[str, Any]: } -@pytest.fixture() +@pytest.fixture def single_layer_cube_args_no_instance_internal() -> Dict[str, Any]: """Dictionary of valid arguments that align to a single non-instance test layer request with internal pygeoapi keys. diff --git a/ogc/edr/test/test_edr_routes.py b/ogc/edr/test/test_edr_routes.py index 9b245a1..7d5a13a 100644 --- a/ogc/edr/test/test_edr_routes.py +++ b/ogc/edr/test/test_edr_routes.py @@ -372,12 +372,13 @@ def test_edr_routes_collection_query_invalid_bbox(layers: List[pogc.Layer], sing single_layer_cube_args["bbox"] = "invalid" request = mock_request(single_layer_cube_args) edr_routes = EdrRoutes(layers=layers) + instance = next(iter(layers[0].time_instances())) with pytest.raises(EDRException) as exception_info: edr_routes.collection_query( request, collection_id=layers[0].group, - instance_id=next(iter(layers[0].time_instances())), + instance_id=instance, query_type="cube", ) diff --git a/ogc/test/test_core.py b/ogc/test/test_core.py index 60be164..2c236b1 100644 --- a/ogc/test/test_core.py +++ b/ogc/test/test_core.py @@ -227,16 +227,16 @@ def test_ogc_core_handle_wms_kv_get_capabilities_hierachical_layers(): root = lxml.etree.fromstring(response.encode("utf-8")) layers = root.xpath(f".//{capability}/{layer}/{title}/text()") - assert {ogc.service_group_title} == set(layers) + assert set(layers) == {ogc.service_group_title} layers = root.xpath(f".//{capability}/{layer}/{layer}/{title}/text()") - assert {layer_root.title, layer_nested.group_path[0]} == set(layers) + assert set(layers) == {layer_root.title, layer_nested.group_path[0]} layers = root.xpath(f".//{capability}/{layer}/{layer}/{layer}/{title}/text()") - assert {layer_nested.group_path[1]} == set(layers) + assert set(layers) == {layer_nested.group_path[1]} layers = root.xpath(f".//{capability}/{layer}/{layer}/{layer}/{layer}/{title}/text()") - assert {layer_nested.title} == set(layers) + assert set(layers) == {layer_nested.title} def test_ogc_core_handle_wms_kv_get_capabilities_invalid_service(): diff --git a/ogc/test/test_input_security.py b/ogc/test/test_input_security.py index e8f231b..1ed0963 100644 --- a/ogc/test/test_input_security.py +++ b/ogc/test/test_input_security.py @@ -32,7 +32,7 @@ def client(): ogc_instance = core.OGC(layers=[layer]) app = servers.FlaskServer(__name__, ogcs=[ogc_instance]) app.config["TESTING"] = True - yield app.test_client() + return app.test_client() # --------------------------------------------------------------------------- diff --git a/ogc/test/test_input_validation.py b/ogc/test/test_input_validation.py index 0874d3e..bd2e5eb 100644 --- a/ogc/test/test_input_validation.py +++ b/ogc/test/test_input_validation.py @@ -11,7 +11,7 @@ from ogc import servers from ogc import settings from ogc import podpac as pogc -from ogc.settings import EDR_TIME_INSTANCE_DIMENSION +from ogc.settings import EDR_TIME_INSTANCE_DIMENSION, crs_84_uri_format lat = np.linspace(90, -90, 11) lon = np.linspace(-180, 180, 21) @@ -73,7 +73,7 @@ def client(): """ Create a test client for the Flask server. - Yields + Returns ------ client : FlaskClient A test client for the Flask server. @@ -84,7 +84,7 @@ def client(): # Create a FlaskServer instance app = servers.FlaskServer(__name__, ogcs=[ogc]) app.config.update({"TESTING": True}) - yield app.test_client() + return app.test_client() def make_valid_ogc_wms_get_capabilities_args() -> dict: @@ -332,7 +332,7 @@ def make_valid_ogc_edr_static_cube_args(layer: str) -> dict: return { "f": "CoverageJSON", "bbox": "-180,-90,180,90", - "crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + "crs": crs_84_uri_format, "parameter-name": layer, "resolution-x": 512, "resolution-y": 512, @@ -355,7 +355,7 @@ def make_valid_ogc_edr_static_area_args(layer: str) -> dict: return { "f": "CoverageJSON", "coords": "POLYGON((-180 90, -180 -90, 180 -90, 180 90, -180 90))", - "crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + "crs": crs_84_uri_format, "parameter-name": layer, "resolution-x": 512, "resolution-y": 512, @@ -378,7 +378,7 @@ def make_valid_ogc_edr_static_position_args(layer: str) -> dict: return { "f": "CoverageJSON", "coords": "POINT(40 50)", - "crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + "crs": crs_84_uri_format, "parameter-name": layer, } @@ -401,7 +401,7 @@ def make_valid_ogc_edr_instance_cube_args(layer: str, time: str) -> dict: return { "f": "CoverageJSON", "bbox": "-180,-90,180,90", - "crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + "crs": crs_84_uri_format, "datetime": time, "parameter-name": layer, "resolution-x": 512, @@ -427,7 +427,7 @@ def make_valid_ogc_edr_instance_area_args(layer: str, time: str) -> dict: return { "f": "CoverageJSON", "coords": "POLYGON((-180 90, -180 -90, 180 -90, 180 90, -180 90))", - "crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + "crs": crs_84_uri_format, "datetime": time, "parameter-name": layer, "resolution-x": 512, @@ -453,7 +453,7 @@ def make_valid_ogc_edr_instance_position_args(layer: str, time: str) -> dict: return { "f": "CoverageJSON", "coords": "POINT(40 50)", - "crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + "crs": crs_84_uri_format, "datetime": time, "parameter-name": layer, } diff --git a/ogc/test/test_servers.py b/ogc/test/test_servers.py index 6bc3423..e8ccc30 100644 --- a/ogc/test/test_servers.py +++ b/ogc/test/test_servers.py @@ -19,7 +19,7 @@ def client(): """ Create a test client for the Flask server. - Yields + Returns ------ client : FlaskClient A test client for the Flask server. @@ -44,7 +44,7 @@ def client(): # Create a FlaskServer instance app = servers.FlaskServer(__name__, ogcs=[ogc]) app.config.update({"TESTING": True}) - yield app.test_client() + return app.test_client() @pytest.fixture @@ -54,7 +54,7 @@ def client_with_percent_and_caret_layer(): contains a % and a ^, mirroring real-world layer names such as "Relative_humidity_[%]" and "Total_precipitation_[kg/(m^2)]". - Yields + Returns ------ client : FlaskClient A test client for the Flask server. @@ -75,7 +75,7 @@ def client_with_percent_and_caret_layer(): ogc = core.OGC(layers=[layer]) app = servers.FlaskServer(__name__, ogcs=[ogc]) app.config.update({"TESTING": True}) - yield app.test_client() + return app.test_client() @pytest.fixture diff --git a/ogc/wcs_response_1_0_0.py b/ogc/wcs_response_1_0_0.py index d25017a..1a76fb3 100755 --- a/ogc/wcs_response_1_0_0.py +++ b/ogc/wcs_response_1_0_0.py @@ -174,7 +174,7 @@ def coverage_offering(self, coverage): xml += "\n".join( [ " {epsg}".format(epsg=epsg.upper()) - for epsg in list(settings.WCS_CRS.keys()) + for epsg in settings.WCS_CRS.keys() ] ) xml += """ diff --git a/ogc/wms_request_1_3_0.py b/ogc/wms_request_1_3_0.py index 37b9d95..77bd389 100755 --- a/ogc/wms_request_1_3_0.py +++ b/ogc/wms_request_1_3_0.py @@ -69,7 +69,7 @@ def validate(self): lons = [self.bbox.lower_corner[0], self.bbox.upper_corner[0]] lats = [self.bbox.lower_corner[1], self.bbox.upper_corner[1]] bbox = lons + lats - if any([abs(round(x, 9)) > 20037508.342789244 for x in bbox]): + if any(abs(round(x, 9)) > 20037508.342789244 for x in bbox): raise ogc_common.WCSException( exception_code="InvalidParameterValue", locator="BBOX", diff --git a/ogc/wms_response_1_3_0.py b/ogc/wms_response_1_3_0.py index 51401de..dc20cae 100755 --- a/ogc/wms_response_1_3_0.py +++ b/ogc/wms_response_1_3_0.py @@ -169,7 +169,7 @@ def coverage_layer(self, coverage: Coverage, depth: int) -> str: None, ) except AttributeError: - latest_lis_time = next((t for t in reversed(coverage.layer.valid_times)), None) + latest_lis_time = next((reversed(coverage.layer.valid_times)), None) if latest_lis_time is not None: # default to latest LIS time, if available @@ -303,7 +303,7 @@ def _get_CRS_and_BoundingBox(self, depth=3): "\n".join( [ INDENT * depth + """{epsg}""".format(epsg=epsg.upper()) - for epsg, bbox in list(settings.WMS_CRS.items()) + for epsg, bbox in settings.WMS_CRS.items() ] ) + "\n" @@ -329,7 +329,7 @@ def _get_CRS_and_BoundingBox(self, depth=3): maxx=Capabilities._format_number(bbox["maxx"]), maxy=Capabilities._format_number(bbox["maxy"]), ) - for epsg, bbox in list(settings.WMS_CRS.items()) + for epsg, bbox in settings.WMS_CRS.items() ] ) return output_text diff --git a/ogc/wmts/test/conftest.py b/ogc/wmts/test/conftest.py index a331778..826cdda 100644 --- a/ogc/wmts/test/conftest.py +++ b/ogc/wmts/test/conftest.py @@ -35,7 +35,7 @@ ) -@pytest.fixture() +@pytest.fixture def layers() -> List[pogc.Layer]: """List of test layers. @@ -47,7 +47,7 @@ def layers() -> List[pogc.Layer]: return [layer1, layer2] -@pytest.fixture() +@pytest.fixture def coverages() -> List[Coverage]: """List of coverages based on layers. diff --git a/ogc/wmts/test/test_wmts_routes.py b/ogc/wmts/test/test_wmts_routes.py index c71153c..bd9e966 100644 --- a/ogc/wmts/test/test_wmts_routes.py +++ b/ogc/wmts/test/test_wmts_routes.py @@ -51,7 +51,8 @@ def test_handle_wmts_kv_get_capabilities_from_ogc_core(layers: List[pogc.Layer]) } response = ogc.handle_wmts_kv(args) assert isinstance(response, str) - assert "Capabilities" in response and "wmts/1.0" in response + assert "Capabilities" in response + assert "wmts/1.0" in response def test_handle_kv_get_capabilities(coverages: List[Coverage]): @@ -71,7 +72,8 @@ def test_handle_kv_get_capabilities(coverages: List[Coverage]): } response = wmts_routes.handle_kv(args) assert isinstance(response, str) - assert "Capabilities" in response and "wmts/1.0" in response + assert "Capabilities" in response + assert "wmts/1.0" in response def test_handle_kv_get_capabilities_invalid_service(coverages: List[Coverage]): From c1454b3822a22cc20be4129788b9eed0d206efc2 Mon Sep 17 00:00:00 2001 From: scranford1 Date: Fri, 28 Aug 2026 11:49:57 -0400 Subject: [PATCH 2/3] Bugfix/get coordinates (#45) --- ogc/podpac.py | 43 +++++++++++++---- ogc/test/test_podpac.py | 103 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 ogc/test/test_podpac.py diff --git a/ogc/podpac.py b/ogc/podpac.py index e2103cd..8f97f3e 100755 --- a/ogc/podpac.py +++ b/ogc/podpac.py @@ -4,7 +4,7 @@ import ogc import podpac -from podpac.core.coordinates import Coordinates +from podpac.core.coordinates import Coordinates, union, merge_dims import traitlets as tl from typing import List from matplotlib import pyplot as plt @@ -80,27 +80,50 @@ def time_instances(self) -> List[str]: def get_coordinates(self) -> Coordinates | None: """Retrieve the coordinates from the node. + This enforces that all coordinates implement unstacked latitude and longitude dimensions. + Returns ------- Coordinates | None Coordinates from the node or None if not found. + + Raises + ------ + ValueError + If any coordinates do not have unstacked latitude and longitude dimensions. """ if self.node is None: return None + output_coordinates_list = [] + shared_dims = ["lat", "lon"] coordinates_list = self.node.find_coordinates() - dimension_set = set() - coordinates = None - for coords in coordinates_list: - dimension_set.update(coords.udims) - if coordinates is None or len(coords.udims) > len(coordinates.udims): - coordinates = coords + if len(coordinates_list) == 0: + return None + + # Verify all coordinates define unstacked latitude and longitude + if not all(dim in coordinates.dims for coordinates in coordinates_list for dim in shared_dims): + raise ValueError("Invalid dimensions for coordinate retrieval.") + + # Only use one source for shared coordinates + shared_coordinates_source = coordinates_list[0] + for dim in shared_dims: + output_coordinates_list.append( + Coordinates( + [shared_coordinates_source[dim].coordinates], + dims=[dim], + crs=shared_coordinates_source.crs, + ) + ) - if coordinates is not None and not all(dim in coordinates.udims for dim in dimension_set): - raise ValueError("Not all node coordinate dimensions contained in the layer coordinates.") + # Use all sources for remaining dimensions, removing duplicates and enforce matching CRS + remaining_coords = union( + [coords.drop(shared_dims).transform(shared_coordinates_source.crs) for coords in coordinates_list] + ) + output_coordinates_list.append(remaining_coords) - return coordinates + return merge_dims(output_coordinates_list) def get_units(self) -> str | None: """Retrieve the units from the node. diff --git a/ogc/test/test_podpac.py b/ogc/test/test_podpac.py new file mode 100644 index 0000000..32860b4 --- /dev/null +++ b/ogc/test/test_podpac.py @@ -0,0 +1,103 @@ +import pytest +import podpac +from ogc import podpac as pogc + +LAT = [0.0, 1.0, 2.0] +LON = [10.0, 20.0, 30.0] +TIME = ["2020-01-01", "2020-01-02"] +TIME2 = ["2020-01-02", "2020-01-03"] +ALT = [1.0, 2.0] +CRS_LATLON = "EPSG:4326" +CRS_OTHER = "EPSG:2193" + + +class MockNode(podpac.Node): + """A mock node for layer coordinate testing.""" + + def __init__(self, coordinates_list): + self._coordinates_list = coordinates_list + + def find_coordinates(self): + return self._coordinates_list + + +class TestLayerGetCoordinates: + def test_node_is_none(self): + """Return value should be None when underlying node is None.""" + layer = pogc.Layer() + assert layer.get_coordinates() is None + + def test_no_coordinates_found(self): + """Return value should be None when no underlying coordinates exist.""" + layer = pogc.Layer(node=MockNode([])) + assert layer.get_coordinates() is None + + def test_unstacked_lat_lon(self): + """Test that unstacked latitude and longitude is valid.""" + source = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON) + layer = pogc.Layer(node=MockNode([source])) + + coordinates = layer.get_coordinates() + + assert coordinates is not None + assert set(coordinates.dims) == {"lat", "lon", "time"} + + def test_stacked_lat_lon(self): + """Test that stacked latitude and longitude is invalid.""" + source = pogc.Coordinates([[LAT, LON], TIME], dims=["lat_lon", "time"], crs=CRS_LATLON) + layer = pogc.Layer(node=MockNode([source])) + + with pytest.raises(ValueError, match="Invalid dimensions for coordinate retrieval."): + layer.get_coordinates() + + def test_no_spatial_dims(self): + """Test that base spatial dimensions are required.""" + source1 = pogc.Coordinates([TIME], dims=["time"], crs=CRS_LATLON) + source2 = pogc.Coordinates([TIME2], dims=["time"], crs=CRS_LATLON) + layer = pogc.Layer(node=MockNode([source1, source2])) + + with pytest.raises(ValueError, match="Invalid dimensions for coordinate retrieval."): + layer.get_coordinates() + + def test_mixed_stacked_and_unstacked_sources(self): + """Test that stacked and unstacked coordinates cannot be mixed together.""" + stacked_source = pogc.Coordinates([[LAT, LON], TIME], dims=["lat_lon", "time"], crs=CRS_LATLON) + unstacked_source = pogc.Coordinates([LAT, LON, TIME2], dims=["lat", "lon", "time"], crs=CRS_LATLON) + layer = pogc.Layer(node=MockNode([stacked_source, unstacked_source])) + + with pytest.raises(ValueError, match="Invalid dimensions for coordinate retrieval."): + layer.get_coordinates() + + def test_crs_mismatch_is_reprojected(self): + """Test that CRS mismatch is transformed to the coordinates which spatial dimensions were pulled from.""" + source1 = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON) + source2 = pogc.Coordinates([LAT, LON, TIME2], dims=["lat", "lon", "time"], crs=CRS_OTHER) + layer = pogc.Layer(node=MockNode([source1, source2])) + + coordinates = layer.get_coordinates() + + assert coordinates is not None + assert coordinates.crs == CRS_LATLON + + def test_duplicate_dims_are_deduped(self): + """Test that duplicate values are removed.""" + source1 = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON) + source2 = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON) + layer = pogc.Layer(node=MockNode([source1, source2])) + + coordinates = layer.get_coordinates() + + assert coordinates is not None + assert coordinates["time"].size == len(TIME) + + def test_dims_are_combined(self): + """Test that dimension values are combined.""" + source1 = pogc.Coordinates([LAT, LON, TIME], dims=["lat", "lon", "time"], crs=CRS_LATLON) + source2 = pogc.Coordinates([LAT, LON, TIME2], dims=["lat", "lon", "time"], crs=CRS_LATLON) + source3 = pogc.Coordinates([LAT, LON], dims=["lat", "lon"], crs=CRS_LATLON) + layer = pogc.Layer(node=MockNode([source1, source2, source3])) + + coordinates = layer.get_coordinates() + + assert coordinates is not None + assert coordinates["time"].size == 3 From 5e77e66a26d9d2f1bd7c7a49b98e9b61288a9510 Mon Sep 17 00:00:00 2001 From: Sam Cranford Date: Fri, 28 Aug 2026 16:04:59 +0000 Subject: [PATCH 3/3] Release 1.0.1 --- CHANGELOG.md | 10 ++++++++++ ogc/version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eea287..63183da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ # Changelog +## 1.0.1 +### Introduction +Address SonarQube findings and fix coordinate retrieval for layers. + +### Maintenance +* Addressed SonarQube findings. + +### Bugfixes +* Fixed layer coordinate retrieval to allow multiple coordinates in a list to be combined together. + ## 1.0.0 Summary release of all features since inception. diff --git a/ogc/version.py b/ogc/version.py index a1c3321..ee7ecbd 100755 --- a/ogc/version.py +++ b/ogc/version.py @@ -17,7 +17,7 @@ ####################### MAJOR = 1 MINOR = 0 -HOTFIX = 0 +HOTFIX = 1 #######################