Skip to content

Commit

Permalink
Keep 'ci/dependabot-updates' up-to-date with 'master'
Browse files Browse the repository at this point in the history
  • Loading branch information
TEAM4-0 committed Mar 15, 2022
2 parents bb4de34 + 4a011df commit c0c584d
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 29 deletions.
4 changes: 3 additions & 1 deletion app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from app.routers import (
datafilter,
dataresource,
function,
mapping,
redisadmin,
session,
Expand Down Expand Up @@ -59,9 +60,10 @@ def create_app() -> FastAPI:
for router_module in (
session,
dataresource,
transformation,
datafilter,
function,
mapping,
transformation,
redisadmin,
):
app.include_router(
Expand Down
38 changes: 38 additions & 0 deletions app/models/function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Function-specific pydantic response models."""
from uuid import uuid4

from pydantic import Field

from app.models.response import CreateResponse, GetResponse, InitializeResponse

IDPREFIX = "function"


class CreateFunctionResponse(CreateResponse):
"""### Create a function
Router: `POST /function`
"""

function_id: str = Field(
default_factory=lambda: f"{IDPREFIX}-{uuid4()}",
description="The function id.",
regex=(
rf"^{IDPREFIX}-[0-9a-f]{{8}}-[0-9a-f]{{4}}-[0-9a-f]{{4}}-[0-9a-f]{{4}}-"
r"[0-9a-f]{12}$"
),
)


class GetFunctionResponse(GetResponse):
"""### Get a function
Router: `GET /function/{function_id}`
"""


class InitializeFunctionResponse(InitializeResponse):
"""### Initialize a function
Router: `POST /function/{function_id}/initialize`
"""
121 changes: 121 additions & 0 deletions app/routers/function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""Function."""
import json
from typing import TYPE_CHECKING, Optional

from aioredis import Redis
from fastapi import APIRouter, Depends, status
from fastapi_plugins import depends_redis
from oteapi.models import FunctionConfig
from oteapi.plugins import create_strategy

from app.models.error import HTTPNotFoundError, httpexception_404_item_id_does_not_exist
from app.models.function import (
IDPREFIX,
CreateFunctionResponse,
GetFunctionResponse,
InitializeFunctionResponse,
)
from app.routers.session import _update_session, _update_session_list_item

if TYPE_CHECKING: # pragma: no cover
from typing import Any, Dict

ROUTER = APIRouter(prefix=f"/{IDPREFIX}")


@ROUTER.post(
"/",
response_model=CreateFunctionResponse,
responses={status.HTTP_404_NOT_FOUND: {"model": HTTPNotFoundError}},
)
async def create_function(
config: FunctionConfig,
session_id: Optional[str] = None,
cache: Redis = Depends(depends_redis),
) -> CreateFunctionResponse:
"""Create a new function configuration."""
new_function = CreateFunctionResponse()

await cache.set(new_function.function_id, config.json())

if session_id:
if not await cache.exists(session_id):
raise httpexception_404_item_id_does_not_exist(session_id, "session_id")
await _update_session_list_item(
session_id=session_id,
list_key="function_info",
list_items=[new_function.function_id],
redis=cache,
)

return new_function


@ROUTER.get(
"/{function_id}",
response_model=GetFunctionResponse,
responses={
status.HTTP_404_NOT_FOUND: {"model": HTTPNotFoundError},
},
)
async def get_function(
function_id: str,
session_id: Optional[str] = None,
cache: Redis = Depends(depends_redis),
) -> GetFunctionResponse:
"""Get (execute) function."""
if not await cache.exists(function_id):
raise httpexception_404_item_id_does_not_exist(function_id, "function_id")
if session_id and not await cache.exists(session_id):
raise httpexception_404_item_id_does_not_exist(session_id, "session_id")

config = FunctionConfig(**json.loads(await cache.get(function_id)))

function_strategy = create_strategy("function", config)
session_data: "Optional[Dict[str, Any]]" = (
None if not session_id else json.loads(await cache.get(session_id))
)
session_update = function_strategy.get(session=session_data)

if session_update and session_id:
await _update_session(
session_id=session_id, updated_session=session_update, redis=cache
)

return GetFunctionResponse(**session_update)


@ROUTER.post(
"/{function_id}/initialize",
response_model=InitializeFunctionResponse,
responses={
status.HTTP_404_NOT_FOUND: {"model": HTTPNotFoundError},
},
)
async def initialize_function(
function_id: str,
session_id: Optional[str] = None,
cache: Redis = Depends(depends_redis),
) -> InitializeFunctionResponse:
"""Initialize and update function."""
if not await cache.exists(function_id):
raise httpexception_404_item_id_does_not_exist(function_id, "function_id")
if session_id and not await cache.exists(session_id):
raise httpexception_404_item_id_does_not_exist(session_id, "session_id")

config = FunctionConfig(**json.loads(await cache.get(function_id)))

function_strategy = create_strategy("function", config)
session_data: "Optional[Dict[str, Any]]" = (
None if not session_id else json.loads(await cache.get(session_id))
)
session_update = function_strategy.initialize(session=session_data)

if session_update and session_id:
await _update_session(
session_id=session_id,
updated_session=session_update,
redis=cache,
)

return InitializeFunctionResponse(**session_update)
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def test_data() -> "Dict[str, dict]":
"filterType": "filter/demo",
"configuration": {"demo_data": [1, 2]},
},
# function
"function-a647012a-7ab9-4f2c-9c13-2564aa6d95a1": {
"functionType": "function/demo",
"configuration": {},
},
# mapping
"mapping-a2d6b3d5-9b6b-48a3-8756-ae6d4fd6b81e": {
"mappingType": "mapping/demo",
Expand Down Expand Up @@ -101,6 +106,11 @@ def load_test_strategies() -> None:
"value": "tests.static.test_strategies.filter:DemoFilter",
"group": "oteapi.filter",
},
{
"name": "tests.function/demo",
"value": "tests.static.test_strategies.function:DemoFunctionStrategy",
"group": "oteapi.function",
},
{
"name": "tests.mapping/demo",
"value": "tests.static.test_strategies.mapping:DemoMappingStrategy",
Expand Down
32 changes: 32 additions & 0 deletions tests/routers/test_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Test function."""
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from fastapi.testclient import TestClient


def test_create_function(client: "TestClient") -> None:
"""Test creating a function."""
response = client.post(
"/function/",
json={
"functionType": "function/demo",
"configuration": {},
},
)
assert "function_id" in response.json()
assert response.status_code == 200


def test_get_function(client: "TestClient") -> None:
"""Test getting a function."""
response = client.get("/function/function-a647012a-7ab9-4f2c-9c13-2564aa6d95a1")
assert response.status_code == 200


def test_initialize_function(client: "TestClient") -> None:
"""Test initializing a function."""
response = client.post(
"/function/function-a647012a-7ab9-4f2c-9c13-2564aa6d95a1/initialize", json={}
)
assert response.status_code == 200
7 changes: 3 additions & 4 deletions tests/static/test_strategies/download.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
"""Demo download strategy class for file."""
# pylint: disable=no-self-use,unused-argument
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional

from oteapi.datacache.datacache import DataCache
from oteapi.models.resourceconfig import ResourceConfig
from pydantic import BaseModel, Extra, Field
from pydantic.dataclasses import dataclass

if TYPE_CHECKING:
from typing import Any, Dict

from oteapi.models.resourceconfig import ResourceConfig


class FileConfig(BaseModel):
"""File Specific Configuration"""
Expand All @@ -29,7 +28,7 @@ class FileConfig(BaseModel):
class FileStrategy:
"""Strategy for retrieving data via local file."""

resource_config: "ResourceConfig"
resource_config: ResourceConfig

def initialize(
self, session: "Optional[Dict[str, Any]]" = None
Expand Down
7 changes: 3 additions & 4 deletions tests/static/test_strategies/filter.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
"""Demo filter strategy."""
# pylint: disable=no-self-use,unused-argument
from dataclasses import dataclass
from typing import TYPE_CHECKING, List

from oteapi.models.filterconfig import FilterConfig
from pydantic import BaseModel, Field
from pydantic.dataclasses import dataclass

if TYPE_CHECKING:
from typing import Any, Dict, Optional

from oteapi.models.filterconfig import FilterConfig


class DemoDataModel(BaseModel):
"""Demo filter data model."""
Expand All @@ -21,7 +20,7 @@ class DemoDataModel(BaseModel):
class DemoFilter:
"""Filter Strategy."""

filter_config: "FilterConfig"
filter_config: FilterConfig

def initialize(
self, session: "Optional[Dict[str, Any]]" = None
Expand Down
54 changes: 54 additions & 0 deletions tests/static/test_strategies/function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Demo function strategy class."""
# pylint: disable=no-self-use,unused-argument
from typing import TYPE_CHECKING

from oteapi.models import FunctionConfig, SessionUpdate
from pydantic.dataclasses import dataclass

if TYPE_CHECKING:
from typing import Any, Dict, Optional


@dataclass
class DemoFunctionStrategy:
"""Function Strategy.
**Registers strategies**:
- `("functionType", "function/DEMO")`
"""

function_config: FunctionConfig

def initialize(self, session: "Optional[Dict[str, Any]]" = None) -> SessionUpdate:
"""Initialize strategy.
This method will be called through the `/initialize` endpoint of the OTEAPI
Services.
Parameters:
session: A session-specific dictionary context.
Returns:
An update model of key/value-pairs to be stored in the
session-specific context from services.
"""
return SessionUpdate()

def get(self, session: "Optional[Dict[str, Any]]" = None) -> SessionUpdate:
"""Execute the strategy.
This method will be called through the strategy-specific endpoint of the
OTEAPI Services.
Parameters:
session: A session-specific dictionary context.
Returns:
An update model of key/value-pairs to be stored in the
session-specific context from services.
"""
return SessionUpdate()
8 changes: 4 additions & 4 deletions tests/static/test_strategies/mapping.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
"""Demo mapping strategy class."""
# pylint: disable=no-self-use,unused-argument
from dataclasses import dataclass
from typing import TYPE_CHECKING

from oteapi.models.mappingconfig import MappingConfig
from pydantic.dataclasses import dataclass

if TYPE_CHECKING:
from typing import Any, Dict, Optional

from oteapi.models.mappingconfig import MappingConfig


@dataclass
class DemoMappingStrategy:
"""Mapping Strategy."""

mapping_config: "MappingConfig"
mapping_config: MappingConfig

def initialize(
self, session: "Optional[Dict[str, Any]]" = None
Expand Down
7 changes: 3 additions & 4 deletions tests/static/test_strategies/parse.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
"""Demo strategy class for text/json."""
# pylint: disable=no-self-use,unused-argument
import json
from dataclasses import dataclass
from typing import TYPE_CHECKING

from oteapi.datacache.datacache import DataCache
from oteapi.models.resourceconfig import ResourceConfig
from oteapi.plugins.factories import create_strategy
from pydantic.dataclasses import dataclass

if TYPE_CHECKING:
from typing import Any, Dict, Optional

from oteapi.models.resourceconfig import ResourceConfig


@dataclass
class DemoJSONDataParseStrategy:
"""Parse Strategy."""

resource_config: "ResourceConfig"
resource_config: ResourceConfig

def initialize(
self, session: "Optional[Dict[str, Any]]" = None
Expand Down
Loading

0 comments on commit c0c584d

Please sign in to comment.