Skip to content

Commit 1b0594c

Browse files
committed
chore: initial setup of api framework
1 parent 715d08a commit 1b0594c

File tree

20 files changed

+426
-1
lines changed

20 files changed

+426
-1
lines changed

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,22 @@
1-
# APEx Dispatch API
1+
# APEx Dispatch API (FastAPI)
22
Implementation of the APEx Upscaling Service API
3+
4+
5+
## Running the API locally
6+
7+
1. Install the required dependencies:
8+
```bash
9+
pip install -r requirements.txt
10+
```
11+
2. Set up your environment variables in a `.env.local` file
12+
3. Run the FastAPI application:
13+
```bash
14+
uvicorn app.main:app --reload
15+
```
16+
17+
## Running Tests
18+
19+
To run the tests, use the following command:
20+
```bash
21+
pytest
22+
```

app/__init__.py

Whitespace-only changes.

app/auth.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from fastapi_keycloak import FastAPIKeycloak
2+
from .config.settings import settings
3+
4+
# create the FastAPIKeycloak instance — used to protect routes
5+
# The server_url must include trailing slash for library
6+
keycloak = FastAPIKeycloak(
7+
server_url=str(settings.keycloak_server_url),
8+
client_id=settings.keycloak_client_id,
9+
client_secret=settings.keycloak_client_secret,
10+
admin_client_secret=settings.keycloak_client_secret, # optional for admin operations
11+
realm=settings.keycloak_realm,
12+
callback_uri="http://localhost:8000/callback", # for auth code flow if needed
13+
)
14+
15+
# expose a helper dependency for current user
16+
get_current_user = keycloak.get_current_user
17+
get_current_active_user = keycloak.get_current_user

app/config/logger.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import logging
2+
import logging.config
3+
4+
5+
LOGGING_CONFIG = {
6+
"version": 1,
7+
"disable_existing_loggers": False, # keeps Uvicorn's loggers
8+
"formatters": {
9+
"default": {
10+
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
11+
},
12+
},
13+
"handlers": {
14+
"console": {
15+
"class": "logging.StreamHandler",
16+
"formatter": "default",
17+
},
18+
},
19+
"root": { # applies to all loggers unless overridden
20+
"level": "INFO",
21+
"handlers": ["console"],
22+
},
23+
"loggers": {
24+
"uvicorn": {"level": "INFO"},
25+
"uvicorn.error": {"level": "INFO"},
26+
"uvicorn.access": {"level": "INFO"},
27+
# custom API loggers
28+
"routers": {"level": "DEBUG"}, # all your routers
29+
"services": {"level": "DEBUG"}, # all your services
30+
},
31+
}
32+
33+
def setup_logging():
34+
logging.config.dictConfig(LOGGING_CONFIG)

app/config/settings.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from pydantic import AnyHttpUrl, ConfigDict, Field
2+
from pydantic_settings import BaseSettings
3+
4+
class Settings(BaseSettings):
5+
app_name: str = Field("", json_schema_extra={"env": "APP_NAME"})
6+
app_description: str = Field("", json_schema_extra={"env":"APP_DESCRIPTION"})
7+
env: str = Field("development", json_schema_extra={"env":"APP_ENV"})
8+
9+
# Keycloak / OIDC
10+
keycloak_server_url: AnyHttpUrl = Field(None, json_schema_extra={"env":"KEYCLOAK_SERVER_URL"})
11+
keycloak_realm: str = Field(None, json_schema_extra={"env":"KEYCLOAK_REALM"})
12+
keycloak_client_id: str = Field(None, json_schema_extra={"env":"KEYCLOAK_CLIENT_ID"})
13+
keycloak_client_secret: str | None = Field(None, json_schema_extra={"env":"KEYCLOAK_CLIENT_SECRET"})
14+
15+
16+
model_config = ConfigDict(
17+
env_file=".env",
18+
env_file_encoding="utf-8",
19+
extra="allow",
20+
)
21+
22+
23+
settings = Settings()

app/main.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from fastapi import FastAPI
2+
from .config.logger import setup_logging
3+
from .config.settings import settings
4+
from .routers import jobs_status, unit_jobs, health
5+
6+
setup_logging()
7+
8+
app = FastAPI(
9+
title=settings.app_name,
10+
description=settings.app_description,
11+
version="1.0.0",
12+
)
13+
14+
# Register Keycloak - must be done after FastAPI app creation
15+
# keycloak.register(app, prefix="/auth") # mounts OIDC endpoints for login if needed
16+
17+
# include routers
18+
app.include_router(jobs_status.router)
19+
app.include_router(unit_jobs.router)
20+
app.include_router(health.router)

app/platforms/base.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from abc import ABC, abstractmethod
2+
3+
from app.schemas import ProcessingJobSummary
4+
5+
6+
class BaseProcessingPlatform(ABC):
7+
"""
8+
Abstract base class for processing platforms.
9+
Defines the interface for processing jobs and managing platform-specific configurations.
10+
"""
11+
12+
@abstractmethod
13+
def execute_job(self, service_id: str, parameters: dict) -> ProcessingJobSummary:
14+
"""
15+
Execute a processing job on the platform with the given service ID and parameters.
16+
17+
:param service_id: The ID of the service to execute.
18+
:param parameters: The parameters required for the job execution.
19+
:return: A ProcessingJobSummary object containing the job details.
20+
"""
21+
pass

app/platforms/dispatcher.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
from app.platforms.base import BaseProcessingPlatform
3+
from app.platforms.ogc_api_process import OGCAPIProcessPlatform
4+
from app.platforms.openeo import OpenEOPlatform
5+
from app.schemas import ProcessType
6+
7+
8+
def get_processing_platform(service_type: ProcessType) -> BaseProcessingPlatform:
9+
"""
10+
Factory function to get the appropriate processing platform based on the service type.
11+
12+
:param service_type: The type of service for which to get the processing platform.
13+
:return: An instance of a class that implements BaseProcessingPlatform.
14+
"""
15+
if service_type == ProcessType.OPENEO:
16+
return OpenEOPlatform()
17+
elif service_type == ProcessType.OGC_API_PROCESS:
18+
return OGCAPIProcessPlatform()
19+
else:
20+
raise ValueError(f"Unsupported service type: {service_type}")

app/platforms/ogc_api_process.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
import logging
3+
4+
from app.platforms.base import BaseProcessingPlatform
5+
from app.schemas import ProcessingJobSummary
6+
7+
8+
logger = logging.getLogger(__name__)
9+
10+
class OGCAPIProcessPlatform(BaseProcessingPlatform):
11+
"""
12+
OGC API Process processing platform implementation.
13+
This class handles the execution of processing jobs on the OGC API Process platform.
14+
"""
15+
16+
def execute_job(self, service_id: str, parameters: dict) -> ProcessingJobSummary:
17+
"""
18+
Execute a processing job on the OGC API Process platform with the given service ID and parameters.
19+
20+
:param service_id: The ID of the service to execute.
21+
:param parameters: The parameters required for the job execution.
22+
:return: A ProcessingJobSummary object containing the job details.
23+
"""
24+
logger.debug(f"Executing OGC API Process job with service_id={service_id} and parameters={parameters}")
25+
raise NotImplementedError("OGC API Process job execution not implemented yet.")

app/platforms/openeo.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
import logging
3+
4+
from app.platforms.base import BaseProcessingPlatform
5+
from app.schemas import ProcessingJobSummary
6+
7+
8+
logger = logging.getLogger(__name__)
9+
10+
class OpenEOPlatform(BaseProcessingPlatform):
11+
"""
12+
OpenEO processing platform implementation.
13+
This class handles the execution of processing jobs on the OpenEO platform.
14+
"""
15+
16+
def execute_job(self, service_id: str, parameters: dict) -> ProcessingJobSummary:
17+
"""
18+
Execute a processing job on the OpenEO platform with the given service ID and parameters.
19+
20+
:param service_id: The ID of the service to execute.
21+
:param parameters: The parameters required for the job execution.
22+
:return: A ProcessingJobSummary object containing the job details.
23+
"""
24+
logger.debug(f"Executing OpenEO job with service_id={service_id} and parameters={parameters}")
25+
raise NotImplementedError("OpenEO job execution not implemented yet.")

0 commit comments

Comments
 (0)