Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions .devcontainer/dev_container.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,21 @@ 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}
ENV TMPDIR=/tmp
WORKDIR /home/${HOST_USER}

USER ${HOST_USER}
ENV PATH "/home/${HOST_USER}/.local/bin:$PATH"
4 changes: 2 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
4 changes: 2 additions & 2 deletions example/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="/ogc_full"> FULL </a> and <a href="/ogc"> PARTIAL </a> endpoints."""


@app.route("/layers/<layer>")
@app.route("/layers/<layer>", methods=["GET"])
def check_layers(layer):
match_object = re.match("[a-zA-Z0-9]+", layer)

Expand Down
6 changes: 3 additions & 3 deletions ogc/edr/edr_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions ogc/edr/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def set_env_vars():
importlib.reload(settings)


@pytest.fixture()
@pytest.fixture
def layers() -> List[pogc.Layer]:
"""List of test layers.

Expand All @@ -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.

Expand All @@ -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.

Expand All @@ -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.

Expand All @@ -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.

Expand Down
3 changes: 2 additions & 1 deletion ogc/edr/test/test_edr_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)

Expand Down
43 changes: 33 additions & 10 deletions ogc/podpac.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions ogc/test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
2 changes: 1 addition & 1 deletion ogc/test/test_input_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


# ---------------------------------------------------------------------------
Expand Down
18 changes: 9 additions & 9 deletions ogc/test/test_input_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
}

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
}
Expand Down
Loading
Loading