-
Notifications
You must be signed in to change notification settings - Fork 27
ITEP-32484 - Implement GET project_configuration endpoint #220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
e809a3c
Implement GET project_configuration endpoint
maxxgx 1b19f82
add ingress
maxxgx 00d43d5
add rest view
maxxgx 4fe36d0
fix style
maxxgx f9efa74
fix style
maxxgx 80ef130
Merge branch 'main' into max/get-project-configuration-endpoint
maxxgx efa1401
Merge branch 'main' into max/get-project-configuration-endpoint
maxxgx cdd5495
refactor rest view
maxxgx 96e1e0a
fix style
maxxgx 75689bb
fix
maxxgx c02e324
return 403
maxxgx d9c5b5b
fix test
maxxgx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
...ve_ai/services/director/app/communication/controllers/project_configuration_controller.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Copyright (C) 2022-2025 Intel Corporation | ||
# LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE | ||
|
||
from typing import Any | ||
|
||
from geti_configuration_tools.project_configuration import NullProjectConfiguration | ||
|
||
from communication.views.project_configuration_rest_views import ProjectConfigurationRESTViews | ||
from storage.repos.project_configuration_repo import ProjectConfigurationRepo | ||
|
||
from geti_fastapi_tools.exceptions import ProjectNotFoundException | ||
from geti_telemetry_tools import unified_tracing | ||
from geti_types import ProjectIdentifier | ||
|
||
|
||
class ProjectConfigurationRESTController: | ||
@staticmethod | ||
@unified_tracing | ||
def get_configuration( | ||
project_identifier: ProjectIdentifier, | ||
) -> dict[str, Any]: | ||
""" | ||
Retrieves configuration related to a specific project. | ||
|
||
:param project_identifier: Identifier for the project (containing organization_id, workspace_id, and project_id) | ||
:return: Dictionary representation of the project configuration | ||
""" | ||
project_config = ProjectConfigurationRepo(project_identifier).get_project_configuration() | ||
if isinstance(project_config, NullProjectConfiguration): | ||
raise ProjectNotFoundException(project_identifier.project_id) | ||
return ProjectConfigurationRESTViews.project_configuration_to_rest(project_config) |
40 changes: 40 additions & 0 deletions
40
...ctive_ai/services/director/app/communication/endpoints/project_configuration_endpoints.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Copyright (C) 2022-2025 Intel Corporation | ||
# LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE | ||
|
||
import logging | ||
from http import HTTPStatus | ||
from typing import Annotated, Any | ||
|
||
from fastapi import APIRouter, Depends | ||
|
||
from communication.controllers.project_configuration_controller import ProjectConfigurationRESTController | ||
from features.feature_flag_provider import FeatureFlag, FeatureFlagProvider | ||
|
||
from geti_fastapi_tools.dependencies import get_project_identifier, setup_session_fastapi | ||
from geti_fastapi_tools.exceptions import GetiBaseException | ||
from geti_types import ProjectIdentifier | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
project_configuration_prefix_url = ( | ||
"/api/v1/organizations/{organization_id}/workspaces/{workspace_id}/projects/{project_id}" | ||
) | ||
project_configuration_router = APIRouter( | ||
prefix=project_configuration_prefix_url, | ||
tags=["Configuration"], | ||
dependencies=[Depends(setup_session_fastapi)], | ||
) | ||
|
||
|
||
@project_configuration_router.get("/project_configuration") | ||
def get_project_configuration( | ||
project_identifier: Annotated[ProjectIdentifier, Depends(get_project_identifier)], | ||
) -> dict[str, Any]: | ||
"""Retrieve the configuration for a specific project.""" | ||
if not FeatureFlagProvider.is_enabled(FeatureFlag.FEATURE_FLAG_NEW_CONFIGURABLE_PARAMETERS): | ||
raise GetiBaseException( | ||
message="Feature not available", | ||
error_code="feature_not_available", | ||
http_status=HTTPStatus.FORBIDDEN, | ||
) | ||
return ProjectConfigurationRESTController().get_configuration(project_identifier=project_identifier) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
interactive_ai/services/director/app/communication/views/configurable_parameters_to_rest.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# Copyright (C) 2022-2025 Intel Corporation | ||
# LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE | ||
|
||
from typing import Any | ||
|
||
from pydantic import BaseModel | ||
|
||
PYDANTIC_TYPES_MAPPING = { | ||
"integer": "int", | ||
"number": "float", | ||
"boolean": "bool", | ||
"string": "str", | ||
} | ||
|
||
|
||
class ConfigurableParametersRESTViews: | ||
""" | ||
Base class for converting configurable parameters to REST views. | ||
|
||
This class provides methods to transform Pydantic models and their fields | ||
into REST-compatible dictionary representations. | ||
""" | ||
|
||
@staticmethod | ||
def _parameter_to_rest(key: str, value: float | str | bool, json_schema: dict) -> dict[str, Any]: | ||
""" | ||
Convert a single parameter to its REST representation. | ||
|
||
:param key: The parameter name/key | ||
:param value: The parameter value (int, float, string, or boolean) | ||
:param json_schema: The JSON schema for the parameter from the Pydantic model | ||
:return: Dictionary containing the REST representation of the parameter | ||
""" | ||
rest_view = { | ||
"key": key, | ||
"name": json_schema.get("title"), | ||
"description": json_schema.get("description"), | ||
"value": value, | ||
"default_value": json_schema.get("default"), | ||
} | ||
# optional parameter may contain `'anyOf': [{'exclusiveMinimum': 0, 'type': 'integer'}, {'type': 'null'}]` | ||
type_any_of = json_schema.get("anyOf", [{}])[0] | ||
rest_view["type"] = PYDANTIC_TYPES_MAPPING.get(json_schema.get("type", type_any_of.get("type"))) | ||
if rest_view["type"] in ["int", "float"]: | ||
rest_view["min_value"] = json_schema.get("minimum", type_any_of.get("exclusiveMinimum")) | ||
rest_view["max_value"] = json_schema.get("maximum", type_any_of.get("exclusiveMaximum")) | ||
return rest_view | ||
|
||
@classmethod | ||
def configurable_parameters_to_rest( | ||
cls, configurable_parameters: BaseModel | ||
) -> dict[str, Any] | list[dict[str, Any]]: | ||
""" | ||
Convert a Pydantic model of configurable parameters to its REST representation. | ||
|
||
This method processes a Pydantic model containing configuration parameters and transforms it | ||
into a REST view. It handles both simple fields and nested models: | ||
|
||
- Simple fields (int, float, str, bool) are converted to a list of dictionaries with metadata | ||
including key, name, description, value, type, and constraints | ||
- Nested Pydantic models are processed recursively and maintained as nested structures | ||
|
||
The return format depends on the content: | ||
- If only simple parameters exist: returns a list of parameter dictionaries | ||
- If only nested models exist: returns a dictionary mapping nested model names to their contents | ||
- If both exist: returns a list containing parameter dictionaries and nested model dictionary | ||
|
||
:param configurable_parameters: Pydantic model containing configurable parameters | ||
:return: REST representation as either a dictionary of nested models, | ||
a list of parameter dictionaries, or a combined list of both | ||
""" | ||
nested_params: dict[str, Any] = {} | ||
list_params: list[dict[str, Any]] = [] | ||
|
||
for field_name in configurable_parameters.model_fields: | ||
field = getattr(configurable_parameters, field_name) | ||
if isinstance(field, BaseModel): | ||
# If the field is a nested Pydantic model, process it recursively | ||
nested_params[field_name] = cls.configurable_parameters_to_rest(field) | ||
else: | ||
# If the field is a simple type, convert directly to REST view | ||
json_model = configurable_parameters.model_json_schema() | ||
list_params.append( | ||
cls._parameter_to_rest( | ||
key=field_name, | ||
value=field, | ||
json_schema=json_model["properties"][field_name], | ||
) | ||
) | ||
|
||
# Return combined or individual results based on content | ||
if nested_params and list_params: | ||
return [*list_params, nested_params] | ||
return list_params or nested_params |
40 changes: 40 additions & 0 deletions
40
interactive_ai/services/director/app/communication/views/project_configuration_rest_views.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Copyright (C) 2022-2025 Intel Corporation | ||
# LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE | ||
|
||
from geti_configuration_tools.project_configuration import ProjectConfiguration, TaskConfig | ||
|
||
from communication.views.configurable_parameters_to_rest import ConfigurableParametersRESTViews | ||
|
||
|
||
class ProjectConfigurationRESTViews(ConfigurableParametersRESTViews): | ||
""" | ||
Converters between ProjectConfiguration models and their corresponding REST views | ||
""" | ||
|
||
@classmethod | ||
def task_config_to_rest(cls, task_config: TaskConfig) -> dict: | ||
""" | ||
Get the REST view of a task configuration | ||
|
||
:param task_config: Task configuration object | ||
:return: REST view of the task configuration | ||
""" | ||
return { | ||
"task_id": task_config.task_id, | ||
"training": cls.configurable_parameters_to_rest(task_config.training), | ||
"auto_training": cls.configurable_parameters_to_rest(task_config.auto_training), | ||
} | ||
|
||
@classmethod | ||
def project_configuration_to_rest(cls, project_configuration: ProjectConfiguration) -> dict: | ||
""" | ||
Get the REST view of a project configuration | ||
|
||
:param project_configuration: Project configuration object | ||
:return: REST view of the project configuration | ||
""" | ||
return { | ||
"task_configs": [ | ||
cls.task_config_to_rest(task_config) for task_config in project_configuration.task_configs | ||
], | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
...rector/tests/unit/communication/rest_controllers/test_project_configuration_controller.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# Copyright (C) 2022-2025 Intel Corporation | ||
# LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE | ||
|
||
import pytest | ||
from testfixtures import compare | ||
|
||
from communication.controllers.project_configuration_controller import ProjectConfigurationRESTController | ||
from storage.repos.project_configuration_repo import ProjectConfigurationRepo | ||
|
||
from geti_fastapi_tools.exceptions import ProjectNotFoundException | ||
from geti_types import ID, ProjectIdentifier | ||
|
||
|
||
@pytest.fixture | ||
def project_configuration_controller(): | ||
return ProjectConfigurationRESTController() | ||
|
||
|
||
class TestProjectConfigurationRESTController: | ||
def test_get_configuration( | ||
self, | ||
request, | ||
fxt_project_identifier, | ||
project_configuration_controller, | ||
fxt_project_configuration, | ||
fxt_project_configuration_rest_view, | ||
) -> None: | ||
# Arrange | ||
repo = ProjectConfigurationRepo(fxt_project_identifier) | ||
request.addfinalizer(lambda: repo.delete_all()) | ||
repo.save(fxt_project_configuration) | ||
|
||
# Act | ||
result = project_configuration_controller.get_configuration(project_identifier=fxt_project_identifier) | ||
|
||
# Convert to dict to compare with expected output | ||
compare(result, fxt_project_configuration_rest_view, ignore_eq=True) | ||
|
||
def test_get_configuration_not_found(self, project_configuration_controller) -> None: | ||
project_id = ID("dummy_project_id") | ||
workspace_id = ID("dummy_workspace_id") | ||
project_identifier = ProjectIdentifier(workspace_id=workspace_id, project_id=project_id) | ||
|
||
with pytest.raises(ProjectNotFoundException): | ||
project_configuration_controller.get_configuration(project_identifier=project_identifier) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps it is cleaner to create a new
ProjectConfigurationNotFoundException
or to use a custom message indicating that the configuration could not be found.