Skip to content

Commit

Permalink
Refactor update_service method signatures for improved readability
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelmathot committed Jan 24, 2025
1 parent 91affbb commit 96c2b02
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 112 deletions.
18 changes: 11 additions & 7 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@

import os
from pathlib import Path
from typing import Dict, Optional, Any, Literal
from typing import Any, Literal

from attrs import field
import pytest
from fastapi import Depends, Header
from fastapi import Header
from starlette.testclient import TestClient

from titiler.openeo.auth import Auth, FakeBasicAuth, User
from titiler.openeo.auth import Auth, User

DATA_DIR = os.path.join(os.path.dirname(__file__), "fixtures")

StoreType = Literal["local", "duckdb", "parquet"]


@pytest.fixture(params=["local", "duckdb", "parquet"])
def store_type(request) -> StoreType:
"""Parameterize the service store type."""
return request.param


@pytest.fixture
def store_path(tmp_path, store_type: StoreType) -> Path:
"""Create a temporary store path based on store type."""
Expand All @@ -32,17 +33,18 @@ def store_path(tmp_path, store_type: StoreType) -> Path:
path = tmp_path / "services.parquet"
return path


@pytest.fixture(autouse=True)
def app(monkeypatch, store_path, store_type) -> TestClient:
"""Create App with temporary services store."""
# Create fixtures directory if it doesn't exist
Path(DATA_DIR).mkdir(exist_ok=True)

monkeypatch.setenv("TITILER_OPENEO_STAC_API_URL", "https://stac.eoapi.dev")
monkeypatch.setenv("TITILER_OPENEO_SERVICE_STORE_URL", f"{store_path}")

from titiler.openeo.main import app, endpoints

# Override the auth dependency with the mock auth
mock_auth = MockAuth()
app.dependency_overrides[endpoints.auth.validate] = mock_auth.validate
Expand All @@ -54,9 +56,11 @@ class MockAuth(Auth):
"""Mock authentication class for testing."""

def login(self, authorization: str = Header(default=None)) -> Any:
"""Mock login method."""
return {"access_token": "mock_token"}

def validate(self, authorization: str = Header(default=None)) -> User:
"""Mock validate method."""
return User(user_id="test_user")


Expand Down
130 changes: 49 additions & 81 deletions tests/test_services.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Test titiler.openeo services."""

import pytest
from titiler.openeo.models import Service, ServiceInput

def test_add_service(app):
"""Test adding a service."""
Expand All @@ -16,24 +14,21 @@ def test_add_service(app):
"west": 16.1,
"east": 16.6,
"north": 48.6,
"south": 47.2
"south": 47.2,
},
"temporal_extent": ["2017-01-01", "2017-02-01"]
}
"temporal_extent": ["2017-01-01", "2017-02-01"],
},
},
"save1": {
"process_id": "save_result",
"arguments": {
"data": {"from_node": "loadco1"},
"format": "png"
},
"result": True
}
"arguments": {"data": {"from_node": "loadco1"}, "format": "png"},
"result": True,
},
}
},
"type": "xyz",
"title": "Test Service",
"description": "A test service"
"description": "A test service",
}

response = app.post("/services", json=service_input)
Expand Down Expand Up @@ -68,23 +63,20 @@ def test_get_service(app):
"west": 16.1,
"east": 16.6,
"north": 48.6,
"south": 47.2
"south": 47.2,
},
"temporal_extent": ["2017-01-01", "2017-02-01"]
}
"temporal_extent": ["2017-01-01", "2017-02-01"],
},
},
"save1": {
"process_id": "save_result",
"arguments": {
"data": {"from_node": "loadco1"},
"format": "png"
},
"result": True
}
"arguments": {"data": {"from_node": "loadco1"}, "format": "png"},
"result": True,
},
}
},
"type": "xyz",
"title": "Test Service"
"title": "Test Service",
}
create_response = app.post("/services", json=service_input)
assert create_response.status_code == 201
Expand All @@ -95,7 +87,7 @@ def test_get_service(app):
assert service["id"] == service_id
assert service["title"] == service_input["title"]
assert service["type"] == service_input["type"]


def test_get_user_services(app):
"""Test getting services for a specific user."""
Expand All @@ -120,23 +112,20 @@ def test_update_service(app):
"west": 16.1,
"east": 16.6,
"north": 48.6,
"south": 47.2
"south": 47.2,
},
"temporal_extent": ["2017-01-01", "2017-02-01"]
}
"temporal_extent": ["2017-01-01", "2017-02-01"],
},
},
"save1": {
"process_id": "save_result",
"arguments": {
"data": {"from_node": "loadco1"},
"format": "png"
},
"result": True
}
"arguments": {"data": {"from_node": "loadco1"}, "format": "png"},
"result": True,
},
}
},
"type": "xyz",
"title": "Original Title"
"title": "Original Title",
}
create_response = app.post("/services", json=service_input)
assert create_response.status_code == 201
Expand All @@ -154,23 +143,20 @@ def test_update_service(app):
"west": 16.1,
"east": 16.6,
"north": 48.6,
"south": 47.2
"south": 47.2,
},
"temporal_extent": ["2017-01-01", "2017-02-01"]
}
"temporal_extent": ["2017-01-01", "2017-02-01"],
},
},
"save1": {
"process_id": "save_result",
"arguments": {
"data": {"from_node": "loadco1"},
"format": "png"
},
"result": True
}
"arguments": {"data": {"from_node": "loadco1"}, "format": "png"},
"result": True,
},
}
},
"type": "xyz",
"title": "Updated Title"
"title": "Updated Title",
}
response = app.patch(f"/services/{service_id}", json=update_input)
assert response.status_code == 204
Expand All @@ -190,23 +176,20 @@ def test_delete_service(app):
"west": 16.1,
"east": 16.6,
"north": 48.6,
"south": 47.2
"south": 47.2,
},
"temporal_extent": ["2017-01-01", "2017-02-01"]
}
"temporal_extent": ["2017-01-01", "2017-02-01"],
},
},
"save1": {
"process_id": "save_result",
"arguments": {
"data": {"from_node": "loadco1"},
"format": "png"
},
"result": True
}
"arguments": {"data": {"from_node": "loadco1"}, "format": "png"},
"result": True,
},
}
},
"type": "xyz",
"title": "Test Service"
"title": "Test Service",
}
create_response = app.post("/services", json=service_input)
assert create_response.status_code == 201
Expand All @@ -224,33 +207,24 @@ def test_delete_service(app):
def test_service_validation(app):
"""Test service input validation."""
# Missing required process
invalid_input = {
"type": "xyz",
"title": "Test Service"
}
invalid_input = {"type": "xyz", "title": "Test Service"}
response = app.post("/services", json=invalid_input)
assert response.status_code == 422

# Invalid process graph
invalid_input = {
"process": {
"process_graph": {
"loadco1": {
"process_id": "invalid_process",
"arguments": {}
},
"loadco1": {"process_id": "invalid_process", "arguments": {}},
"save1": {
"process_id": "save_result",
"arguments": {
"data": {"from_node": "loadco1"},
"format": "png"
},
"result": True
}
"arguments": {"data": {"from_node": "loadco1"}, "format": "png"},
"result": True,
},
}
},
"type": "xyz",
"title": "Test Service"
"title": "Test Service",
}
response = app.post("/services", json=invalid_input)
assert response.status_code == 400
Expand All @@ -269,27 +243,21 @@ def test_service_configuration(app):
"west": 16.1,
"east": 16.6,
"north": 48.6,
"south": 47.2
"south": 47.2,
},
"temporal_extent": ["2017-01-01", "2017-02-01"]
}
"temporal_extent": ["2017-01-01", "2017-02-01"],
},
},
"save1": {
"process_id": "save_result",
"arguments": {
"data": {"from_node": "loadco1"},
"format": "png"
},
"result": True
}
"arguments": {"data": {"from_node": "loadco1"}, "format": "png"},
"result": True,
},
}
},
"type": "xyz",
"title": "Test Service",
"configuration": {
"version": "1.0.0",
"format": "png"
}
"configuration": {"version": "1.0.0", "format": "png"},
}

response = app.post("/services", json=service_input)
Expand Down
35 changes: 21 additions & 14 deletions titiler/openeo/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
from titiler.openeo import models
from titiler.openeo.auth import Auth, CredentialsBasic, FakeBasicAuth
from titiler.openeo.errors import InvalidProcessGraph
from titiler.openeo.models import (
OPENEO_VERSION,
ServiceInput,
)
from titiler.openeo.models import OPENEO_VERSION, ServiceInput
from titiler.openeo.services import ServicesStore
from titiler.openeo.stacapi import stacApiBackend

Expand Down Expand Up @@ -451,30 +448,36 @@ def openeo_service_create(
):
"""Creates a new secondary web service."""
process = body.process.model_dump()

# Validate process graph
try:
# Parse and validate process graph structure
parsed_graph = OpenEOProcessGraph(pg_data=process)

# Check if all processes exist in registry
for node in parsed_graph.nodes:
process_id = node[1].get("process_id")
if process_id and process_id not in self.process_registry[None]:
raise InvalidProcessGraph(f"Process '{process_id}' not found in registry")

raise InvalidProcessGraph(
f"Process '{process_id}' not found in registry"
)

# Try to create callable to validate parameter types
parsed_graph.to_callable(process_registry=self.process_registry)

except Exception as e:
raise InvalidProcessGraph(f"Invalid process graph: {str(e)}")
raise InvalidProcessGraph(f"Invalid process graph: {str(e)}") from e

service_id = self.services_store.add_service(user.user_id, body.model_dump())
service_id = self.services_store.add_service(
user.user_id, body.model_dump()
)
service = self.services_store.get_service(service_id)
if not service:
raise HTTPException(404, f"Could not find service: {service_id}")
service_url = self.url_for(request, "openeo_service", service_id=service["id"])

service_url = self.url_for(
request, "openeo_service", service_id=service["id"]
)

return Response(
status_code=201,
headers={
Expand Down Expand Up @@ -549,7 +552,9 @@ def openeo_service_update(
user=Depends(self.auth.validate),
):
"""Updates an existing secondary web service."""
self.services_store.update_service(user.user_id, service_id, body.model_dump() if body else {})
self.services_store.update_service(
user.user_id, service_id, body.model_dump() if body else {}
)
return Response(status_code=204)

@self.router.get(
Expand Down Expand Up @@ -748,6 +753,8 @@ def openeo_xyz_service(
service = self.services_store.get_service(service_id)

# SERVICE CONFIGURATION
if service is None:
raise HTTPException(404, f"Could not find service: {service_id}")
configuration = service.get("configuration", {})
tile_size = configuration.get("tile_size", 256)
tile_buffer = configuration.get("buffer")
Expand Down
1 change: 1 addition & 0 deletions titiler/openeo/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def get_store(store_uri: str) -> ServicesStore:

if parsed.path.endswith(".json"):
import json

return LocalStore(json.load(open(store_uri))) # type: ignore

if parsed.path.endswith(".db"):
Expand Down
Loading

0 comments on commit 96c2b02

Please sign in to comment.