refactor(domain): extract shared model_config into a base Pydantic model
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Failing after 19s
CI / typecheck (pull_request) Failing after 56s
CI / security (pull_request) Failing after 56s
CI / coverage (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 2m4s
CI / docker (pull_request) Has been skipped
CI / build (pull_request) Successful in 16s
CI / helm (pull_request) Successful in 23s
CI / quality (pull_request) Successful in 3m42s
CI / e2e_tests (pull_request) Failing after 15m14s
CI / integration_tests (pull_request) Failing after 22m3s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Failing after 19s
CI / typecheck (pull_request) Failing after 56s
CI / security (pull_request) Failing after 56s
CI / coverage (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 2m4s
CI / docker (pull_request) Has been skipped
CI / build (pull_request) Successful in 16s
CI / helm (pull_request) Successful in 23s
CI / quality (pull_request) Successful in 3m42s
CI / e2e_tests (pull_request) Failing after 15m14s
CI / integration_tests (pull_request) Failing after 22m3s
CI / status-check (pull_request) Failing after 1s
Introduce DomainBaseModel in src/cleveragents/domain/models/base.py that defines the single shared model_config (str_strip_whitespace, validate_assignment, arbitrary_types_allowed=False, populate_by_name, use_enum_values) previously duplicated verbatim across five domain model files. Update 14 classes across five files to inherit from DomainBaseModel instead of pydantic.BaseModel directly, removing all inline model_config duplication: - aimodelscredentials/ai_models_credentials.py (ModelProviderOption) - aimodelserrors/ai_models_errors.py (ModelError, FallbackResult) - aimodelsproviders/ai_models_providers.py (ModelProviderExtraAuthVars, ModelProviderConfigSchema) - auth/auth.py (AuthHeader, TrialPlansExceededError, TrialMessagesExceededError, BillingError, ApiError, ClientAccount, ClientAuth) - planconfig/plan_config.py (PlanConfig, ConfigSetting) Pure structural refactor — no behavioral changes. Models with different configurations (acms, aimodels_custom, etc.) are left untouched. Add BDD feature (22 scenarios) and Robot integration tests (8 test cases) verifying inheritance, config correctness, and no-duplication invariants. ISSUES CLOSED: #1941
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
Feature: DomainBaseModel shared configuration
|
||||
As a developer working on the domain layer
|
||||
I want all domain models to inherit from DomainBaseModel
|
||||
So that the shared model_config is defined in exactly one place
|
||||
|
||||
Scenario: DomainBaseModel is importable from the domain models package
|
||||
Given the cleveragents package is available
|
||||
When I import DomainBaseModel from the domain models base module
|
||||
Then the DomainBaseModel class should be available
|
||||
And DomainBaseModel should be a subclass of pydantic BaseModel
|
||||
|
||||
Scenario: DomainBaseModel carries the expected model_config
|
||||
Given the cleveragents package is available
|
||||
When I inspect the DomainBaseModel model_config
|
||||
Then the DomainBaseModel str_strip_whitespace should be True
|
||||
And the DomainBaseModel validate_assignment should be True
|
||||
And the DomainBaseModel arbitrary_types_allowed should be False
|
||||
And the DomainBaseModel populate_by_name should be True
|
||||
And the DomainBaseModel use_enum_values should be True
|
||||
|
||||
Scenario: ModelProviderOption inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import ModelProviderOption from aimodelscredentials
|
||||
Then ModelProviderOption should be a subclass of DomainBaseModel
|
||||
And the ModelProviderOption model_config should match DomainBaseModel
|
||||
|
||||
Scenario: ModelError inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import ModelError from aimodelserrors
|
||||
Then ModelError should be a subclass of DomainBaseModel
|
||||
And the ModelError model_config should match DomainBaseModel
|
||||
|
||||
Scenario: FallbackResult inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import FallbackResult from aimodelserrors
|
||||
Then FallbackResult should be a subclass of DomainBaseModel
|
||||
And the FallbackResult model_config should match DomainBaseModel
|
||||
|
||||
Scenario: ModelProviderExtraAuthVars inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import ModelProviderExtraAuthVars from aimodelsproviders
|
||||
Then ModelProviderExtraAuthVars should be a subclass of DomainBaseModel
|
||||
And the ModelProviderExtraAuthVars model_config should match DomainBaseModel
|
||||
|
||||
Scenario: ModelProviderConfigSchema inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import ModelProviderConfigSchema from aimodelsproviders
|
||||
Then ModelProviderConfigSchema should be a subclass of DomainBaseModel
|
||||
And the ModelProviderConfigSchema model_config should match DomainBaseModel
|
||||
|
||||
Scenario: AuthHeader inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import AuthHeader from auth
|
||||
Then AuthHeader should be a subclass of DomainBaseModel
|
||||
And the AuthHeader model_config should match DomainBaseModel
|
||||
|
||||
Scenario: TrialPlansExceededError inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import TrialPlansExceededError from auth
|
||||
Then TrialPlansExceededError should be a subclass of DomainBaseModel
|
||||
And the TrialPlansExceededError model_config should match DomainBaseModel
|
||||
|
||||
Scenario: TrialMessagesExceededError inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import TrialMessagesExceededError from auth
|
||||
Then TrialMessagesExceededError should be a subclass of DomainBaseModel
|
||||
And the TrialMessagesExceededError model_config should match DomainBaseModel
|
||||
|
||||
Scenario: BillingError inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import BillingError from auth
|
||||
Then BillingError should be a subclass of DomainBaseModel
|
||||
And the BillingError model_config should match DomainBaseModel
|
||||
|
||||
Scenario: ApiError inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import ApiError from auth
|
||||
Then ApiError should be a subclass of DomainBaseModel
|
||||
And the ApiError model_config should match DomainBaseModel
|
||||
|
||||
Scenario: ClientAccount inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import ClientAccount from auth
|
||||
Then ClientAccount should be a subclass of DomainBaseModel
|
||||
And the ClientAccount model_config should match DomainBaseModel
|
||||
|
||||
Scenario: ClientAuth inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import ClientAuth from auth
|
||||
Then ClientAuth should be a subclass of DomainBaseModel
|
||||
And the ClientAuth model_config should match DomainBaseModel
|
||||
|
||||
Scenario: PlanConfig inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import PlanConfig from planconfig
|
||||
Then PlanConfig should be a subclass of DomainBaseModel
|
||||
And the PlanConfig model_config should match DomainBaseModel
|
||||
|
||||
Scenario: ConfigSetting inherits from DomainBaseModel
|
||||
Given the cleveragents package is available
|
||||
When I import ConfigSetting from planconfig
|
||||
Then ConfigSetting should be a subclass of DomainBaseModel
|
||||
And the ConfigSetting model_config should match DomainBaseModel
|
||||
|
||||
Scenario: DomainBaseModel config is applied to ModelProviderOption instances
|
||||
Given the cleveragents package is available
|
||||
When I create a ModelProviderOption with only priority set to 1
|
||||
Then the ModelProviderOption should have priority 1
|
||||
And the ModelProviderOption str_strip_whitespace should be True
|
||||
And the ModelProviderOption validate_assignment should be True
|
||||
And the ModelProviderOption arbitrary_types_allowed should be False
|
||||
And the ModelProviderOption populate_by_name should be True
|
||||
And the ModelProviderOption use_enum_values should be True
|
||||
|
||||
Scenario: No inline model_config duplication in aimodelscredentials
|
||||
Given the cleveragents package is available
|
||||
When I inspect the ModelProviderOption class definition
|
||||
Then ModelProviderOption should not define its own model_config
|
||||
|
||||
Scenario: No inline model_config duplication in aimodelserrors
|
||||
Given the cleveragents package is available
|
||||
When I inspect the ModelError class definition
|
||||
Then ModelError should not define its own model_config
|
||||
|
||||
Scenario: No inline model_config duplication in aimodelsproviders
|
||||
Given the cleveragents package is available
|
||||
When I inspect the ModelProviderConfigSchema class definition
|
||||
Then ModelProviderConfigSchema should not define its own model_config
|
||||
|
||||
Scenario: No inline model_config duplication in auth
|
||||
Given the cleveragents package is available
|
||||
When I inspect the AuthHeader class definition
|
||||
Then AuthHeader should not define its own model_config
|
||||
|
||||
Scenario: No inline model_config duplication in planconfig
|
||||
Given the cleveragents package is available
|
||||
When I inspect the PlanConfig class definition
|
||||
Then PlanConfig should not define its own model_config
|
||||
@@ -0,0 +1,296 @@
|
||||
"""Step definitions for DomainBaseModel shared configuration tests.
|
||||
|
||||
Only steps that are NOT already defined in other step modules are defined here.
|
||||
Steps shared with aimodelscredentials_steps.py, aimodelserrors_steps.py, and
|
||||
aimodelsproviders_steps.py are reused automatically by Behave's global step
|
||||
registry.
|
||||
"""
|
||||
|
||||
from behave import then, when
|
||||
from pydantic import BaseModel
|
||||
|
||||
from cleveragents.domain.models.base import DomainBaseModel
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared expected config values
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_EXPECTED_CONFIG: dict[str, object] = {
|
||||
"str_strip_whitespace": True,
|
||||
"validate_assignment": True,
|
||||
"arbitrary_types_allowed": False,
|
||||
"populate_by_name": True,
|
||||
"use_enum_values": True,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# When — new imports (not defined in existing step files)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when("I import DomainBaseModel from the domain models base module")
|
||||
def step_import_domain_base_model(context: object) -> None:
|
||||
"""Import DomainBaseModel and store on context."""
|
||||
context.domain_base_model_class = DomainBaseModel
|
||||
context.imported_class = DomainBaseModel
|
||||
|
||||
|
||||
@when("I inspect the DomainBaseModel model_config")
|
||||
def step_inspect_domain_base_model_config(context: object) -> None:
|
||||
"""Store the DomainBaseModel model_config on context."""
|
||||
context.inspected_config = DomainBaseModel.model_config
|
||||
|
||||
|
||||
@when("I import ModelError from aimodelserrors")
|
||||
def step_import_model_error(context: object) -> None:
|
||||
"""Import ModelError."""
|
||||
from cleveragents.domain.models.aimodelserrors.ai_models_errors import ModelError
|
||||
|
||||
context.imported_class = ModelError
|
||||
|
||||
|
||||
@when("I import FallbackResult from aimodelserrors")
|
||||
def step_import_fallback_result(context: object) -> None:
|
||||
"""Import FallbackResult."""
|
||||
from cleveragents.domain.models.aimodelserrors.ai_models_errors import (
|
||||
FallbackResult,
|
||||
)
|
||||
|
||||
context.imported_class = FallbackResult
|
||||
|
||||
|
||||
@when("I import ModelProviderExtraAuthVars from aimodelsproviders")
|
||||
def step_import_model_provider_extra_auth_vars(context: object) -> None:
|
||||
"""Import ModelProviderExtraAuthVars."""
|
||||
from cleveragents.domain.models.aimodelsproviders.ai_models_providers import (
|
||||
ModelProviderExtraAuthVars,
|
||||
)
|
||||
|
||||
context.imported_class = ModelProviderExtraAuthVars
|
||||
|
||||
|
||||
@when("I import ModelProviderConfigSchema from aimodelsproviders")
|
||||
def step_import_model_provider_config_schema(context: object) -> None:
|
||||
"""Import ModelProviderConfigSchema."""
|
||||
from cleveragents.domain.models.aimodelsproviders.ai_models_providers import (
|
||||
ModelProviderConfigSchema,
|
||||
)
|
||||
|
||||
context.imported_class = ModelProviderConfigSchema
|
||||
|
||||
|
||||
@when("I import AuthHeader from auth")
|
||||
def step_import_auth_header(context: object) -> None:
|
||||
"""Import AuthHeader."""
|
||||
from cleveragents.domain.models.auth.auth import AuthHeader
|
||||
|
||||
context.imported_class = AuthHeader
|
||||
|
||||
|
||||
@when("I import TrialPlansExceededError from auth")
|
||||
def step_import_trial_plans_exceeded_error(context: object) -> None:
|
||||
"""Import TrialPlansExceededError."""
|
||||
from cleveragents.domain.models.auth.auth import TrialPlansExceededError
|
||||
|
||||
context.imported_class = TrialPlansExceededError
|
||||
|
||||
|
||||
@when("I import TrialMessagesExceededError from auth")
|
||||
def step_import_trial_messages_exceeded_error(context: object) -> None:
|
||||
"""Import TrialMessagesExceededError."""
|
||||
from cleveragents.domain.models.auth.auth import TrialMessagesExceededError
|
||||
|
||||
context.imported_class = TrialMessagesExceededError
|
||||
|
||||
|
||||
@when("I import BillingError from auth")
|
||||
def step_import_billing_error(context: object) -> None:
|
||||
"""Import BillingError."""
|
||||
from cleveragents.domain.models.auth.auth import BillingError
|
||||
|
||||
context.imported_class = BillingError
|
||||
|
||||
|
||||
@when("I import ApiError from auth")
|
||||
def step_import_api_error(context: object) -> None:
|
||||
"""Import ApiError."""
|
||||
from cleveragents.domain.models.auth.auth import ApiError
|
||||
|
||||
context.imported_class = ApiError
|
||||
|
||||
|
||||
@when("I import ClientAccount from auth")
|
||||
def step_import_client_account(context: object) -> None:
|
||||
"""Import ClientAccount."""
|
||||
from cleveragents.domain.models.auth.auth import ClientAccount
|
||||
|
||||
context.imported_class = ClientAccount
|
||||
|
||||
|
||||
@when("I import ClientAuth from auth")
|
||||
def step_import_client_auth(context: object) -> None:
|
||||
"""Import ClientAuth."""
|
||||
from cleveragents.domain.models.auth.auth import ClientAuth
|
||||
|
||||
context.imported_class = ClientAuth
|
||||
|
||||
|
||||
@when("I import PlanConfig from planconfig")
|
||||
def step_import_plan_config(context: object) -> None:
|
||||
"""Import PlanConfig."""
|
||||
from cleveragents.domain.models.planconfig.plan_config import PlanConfig
|
||||
|
||||
context.imported_class = PlanConfig
|
||||
|
||||
|
||||
@when("I import ConfigSetting from planconfig")
|
||||
def step_import_config_setting(context: object) -> None:
|
||||
"""Import ConfigSetting."""
|
||||
from cleveragents.domain.models.planconfig.plan_config import ConfigSetting
|
||||
|
||||
context.imported_class = ConfigSetting
|
||||
|
||||
|
||||
# When — class definition inspection
|
||||
|
||||
|
||||
@when("I inspect the ModelProviderOption class definition")
|
||||
def step_inspect_model_provider_option(context: object) -> None:
|
||||
"""Store ModelProviderOption for inspection."""
|
||||
from cleveragents.domain.models.aimodelscredentials.ai_models_credentials import (
|
||||
ModelProviderOption,
|
||||
)
|
||||
|
||||
context.inspected_class = ModelProviderOption
|
||||
|
||||
|
||||
@when("I inspect the ModelError class definition")
|
||||
def step_inspect_model_error(context: object) -> None:
|
||||
"""Store ModelError for inspection."""
|
||||
from cleveragents.domain.models.aimodelserrors.ai_models_errors import ModelError
|
||||
|
||||
context.inspected_class = ModelError
|
||||
|
||||
|
||||
@when("I inspect the ModelProviderConfigSchema class definition")
|
||||
def step_inspect_model_provider_config_schema_def(context: object) -> None:
|
||||
"""Store ModelProviderConfigSchema for inspection."""
|
||||
from cleveragents.domain.models.aimodelsproviders.ai_models_providers import (
|
||||
ModelProviderConfigSchema,
|
||||
)
|
||||
|
||||
context.inspected_class = ModelProviderConfigSchema
|
||||
|
||||
|
||||
@when("I inspect the AuthHeader class definition")
|
||||
def step_inspect_auth_header(context: object) -> None:
|
||||
"""Store AuthHeader for inspection."""
|
||||
from cleveragents.domain.models.auth.auth import AuthHeader
|
||||
|
||||
context.inspected_class = AuthHeader
|
||||
|
||||
|
||||
@when("I inspect the PlanConfig class definition")
|
||||
def step_inspect_plan_config(context: object) -> None:
|
||||
"""Store PlanConfig for inspection."""
|
||||
from cleveragents.domain.models.planconfig.plan_config import PlanConfig
|
||||
|
||||
context.inspected_class = PlanConfig
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Then — DomainBaseModel assertions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then("the DomainBaseModel class should be available")
|
||||
def step_domain_base_model_available(context: object) -> None:
|
||||
"""Verify DomainBaseModel is available."""
|
||||
assert context.domain_base_model_class is not None
|
||||
assert context.domain_base_model_class.__name__ == "DomainBaseModel"
|
||||
|
||||
|
||||
@then("DomainBaseModel should be a subclass of pydantic BaseModel")
|
||||
def step_domain_base_model_is_pydantic(context: object) -> None:
|
||||
"""Verify DomainBaseModel inherits from pydantic BaseModel."""
|
||||
assert issubclass(context.domain_base_model_class, BaseModel)
|
||||
|
||||
|
||||
@then("the DomainBaseModel str_strip_whitespace should be True")
|
||||
def step_domain_base_model_str_strip(context: object) -> None:
|
||||
"""Verify str_strip_whitespace on DomainBaseModel."""
|
||||
assert context.inspected_config.get("str_strip_whitespace") is True
|
||||
|
||||
|
||||
@then("the DomainBaseModel validate_assignment should be True")
|
||||
def step_domain_base_model_validate_assignment(context: object) -> None:
|
||||
"""Verify validate_assignment on DomainBaseModel."""
|
||||
assert context.inspected_config.get("validate_assignment") is True
|
||||
|
||||
|
||||
@then("the DomainBaseModel arbitrary_types_allowed should be False")
|
||||
def step_domain_base_model_arbitrary_types(context: object) -> None:
|
||||
"""Verify arbitrary_types_allowed on DomainBaseModel."""
|
||||
assert context.inspected_config.get("arbitrary_types_allowed") is False
|
||||
|
||||
|
||||
@then("the DomainBaseModel populate_by_name should be True")
|
||||
def step_domain_base_model_populate_by_name(context: object) -> None:
|
||||
"""Verify populate_by_name on DomainBaseModel."""
|
||||
assert context.inspected_config.get("populate_by_name") is True
|
||||
|
||||
|
||||
@then("the DomainBaseModel use_enum_values should be True")
|
||||
def step_domain_base_model_use_enum_values(context: object) -> None:
|
||||
"""Verify use_enum_values on DomainBaseModel."""
|
||||
assert context.inspected_config.get("use_enum_values") is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Then — subclass assertions (generic, reused for all domain models)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then("{class_name} should be a subclass of DomainBaseModel")
|
||||
def step_is_subclass_of_domain_base_model(context: object, class_name: str) -> None:
|
||||
"""Verify the imported class is a subclass of DomainBaseModel."""
|
||||
assert issubclass(context.imported_class, DomainBaseModel), (
|
||||
f"{class_name} is not a subclass of DomainBaseModel"
|
||||
)
|
||||
|
||||
|
||||
@then("the {class_name} model_config should match DomainBaseModel")
|
||||
def step_config_matches_domain_base_model(context: object, class_name: str) -> None:
|
||||
"""Verify the imported class inherits the expected model_config values."""
|
||||
cfg = context.imported_class.model_config
|
||||
for key, expected in _EXPECTED_CONFIG.items():
|
||||
actual = cfg.get(key)
|
||||
assert actual == expected, (
|
||||
f"{class_name}.model_config[{key!r}] = {actual!r}, expected {expected!r}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Then — no inline model_config duplication assertions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then("{class_name} should not define its own model_config")
|
||||
def step_no_own_model_config(context: object, class_name: str) -> None:
|
||||
"""Verify the class source does not contain an inline model_config assignment.
|
||||
|
||||
Pydantic's metaclass copies model_config into every subclass __dict__
|
||||
automatically, so we inspect the source code instead of __dict__ to
|
||||
confirm no *explicit* duplication exists.
|
||||
"""
|
||||
import inspect
|
||||
|
||||
source = inspect.getsource(context.inspected_class)
|
||||
# Remove the class header line so we only look at the class body
|
||||
body_lines = source.splitlines()[1:]
|
||||
body = "\n".join(body_lines)
|
||||
assert "model_config" not in body, (
|
||||
f"{class_name} has an explicit model_config in its source — "
|
||||
"should inherit from DomainBaseModel without redefinition"
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
*** Settings ***
|
||||
Documentation Integration smoke tests for DomainBaseModel refactor.
|
||||
...
|
||||
... Verifies that extracting the shared model_config into
|
||||
... DomainBaseModel has not changed the observable behaviour of
|
||||
... any affected domain model class.
|
||||
Resource ${CURDIR}/common.resource
|
||||
Suite Setup Setup Test Environment
|
||||
Suite Teardown Cleanup Test Environment
|
||||
|
||||
*** Variables ***
|
||||
${HELPER_SCRIPT} robot/helper_domain_base_model.py
|
||||
|
||||
*** Test Cases ***
|
||||
DomainBaseModel Is Importable
|
||||
[Documentation] DomainBaseModel can be imported and is a pydantic BaseModel subclass
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} importable cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} domain-base-model-importable-ok
|
||||
|
||||
DomainBaseModel Carries Expected Config
|
||||
[Documentation] DomainBaseModel model_config has all expected settings
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} config cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} domain-base-model-config-ok
|
||||
|
||||
ModelProviderOption Behaviour Unchanged
|
||||
[Documentation] ModelProviderOption works correctly after inheriting from DomainBaseModel
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} model-provider-option cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} model-provider-option-ok
|
||||
|
||||
ModelError Behaviour Unchanged
|
||||
[Documentation] ModelError works correctly after inheriting from DomainBaseModel
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} model-error cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} model-error-ok
|
||||
|
||||
FallbackResult Behaviour Unchanged
|
||||
[Documentation] FallbackResult works correctly after inheriting from DomainBaseModel
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} fallback-result cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} fallback-result-ok
|
||||
|
||||
Auth Models Behaviour Unchanged
|
||||
[Documentation] All auth domain models work correctly after inheriting from DomainBaseModel
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} auth-models cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} auth-models-ok
|
||||
|
||||
PlanConfig Models Behaviour Unchanged
|
||||
[Documentation] PlanConfig and ConfigSetting work correctly after inheriting from DomainBaseModel
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} plan-config-models cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} plan-config-models-ok
|
||||
|
||||
Whitespace Stripping Active On Refactored Models
|
||||
[Documentation] str_strip_whitespace config is active on all refactored models
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} whitespace-stripping cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} whitespace-stripping-ok
|
||||
@@ -0,0 +1,239 @@
|
||||
"""Helper script for DomainBaseModel Robot integration tests.
|
||||
|
||||
Verifies that the structural refactor (extracting shared model_config into
|
||||
DomainBaseModel) has not changed the observable behaviour of any affected
|
||||
domain model class.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from cleveragents.domain.models.base import DomainBaseModel
|
||||
|
||||
|
||||
def _test_domain_base_model_importable() -> None:
|
||||
"""DomainBaseModel is importable and is a pydantic BaseModel subclass."""
|
||||
assert issubclass(DomainBaseModel, BaseModel)
|
||||
print("domain-base-model-importable-ok")
|
||||
|
||||
|
||||
def _test_domain_base_model_config() -> None:
|
||||
"""DomainBaseModel carries the expected shared model_config."""
|
||||
cfg = DomainBaseModel.model_config
|
||||
assert cfg.get("str_strip_whitespace") is True
|
||||
assert cfg.get("validate_assignment") is True
|
||||
assert cfg.get("arbitrary_types_allowed") is False
|
||||
assert cfg.get("populate_by_name") is True
|
||||
assert cfg.get("use_enum_values") is True
|
||||
print("domain-base-model-config-ok")
|
||||
|
||||
|
||||
def _test_model_provider_option() -> None:
|
||||
"""ModelProviderOption behaviour is unchanged after refactor."""
|
||||
from cleveragents.domain.models.aimodelscredentials.ai_models_credentials import (
|
||||
ModelProviderOption,
|
||||
)
|
||||
from cleveragents.domain.models.aimodelsproviders.ai_models_providers import (
|
||||
ModelProviderConfigSchema,
|
||||
)
|
||||
from cleveragents.domain.models.core.enums import ModelProvider, ModelPublisher
|
||||
|
||||
assert issubclass(ModelProviderOption, DomainBaseModel)
|
||||
|
||||
config = ModelProviderConfigSchema(
|
||||
provider=ModelProvider.OPENAI, baseUrl="https://api.openai.com/v1"
|
||||
)
|
||||
publishers = {"primary": {ModelPublisher.OPENAI: True}}
|
||||
model = ModelProviderOption(publishers=publishers, config=config, priority=1)
|
||||
|
||||
assert model.priority == 1
|
||||
assert model.config is not None
|
||||
assert model.config.base_url == "https://api.openai.com/v1"
|
||||
# Verify str_strip_whitespace is active via inherited config
|
||||
assert model.model_config.get("str_strip_whitespace") is True
|
||||
print("model-provider-option-ok")
|
||||
|
||||
|
||||
def _test_model_error() -> None:
|
||||
"""ModelError behaviour is unchanged after refactor."""
|
||||
from cleveragents.domain.models.aimodelserrors.ai_models_errors import ModelError
|
||||
from cleveragents.domain.models.core.enums import ModelErrKind
|
||||
|
||||
assert issubclass(ModelError, DomainBaseModel)
|
||||
|
||||
err = ModelError(kind=ModelErrKind.OVERLOADED, retriable=True, retryafterseconds=30)
|
||||
assert err.retriable is True
|
||||
assert err.retryafterseconds == 30
|
||||
print("model-error-ok")
|
||||
|
||||
|
||||
def _test_fallback_result() -> None:
|
||||
"""FallbackResult behaviour is unchanged after refactor."""
|
||||
from cleveragents.domain.models.aimodelserrors.ai_models_errors import (
|
||||
FallbackResult,
|
||||
)
|
||||
from cleveragents.domain.models.core.enums import FallbackType
|
||||
|
||||
assert issubclass(FallbackResult, DomainBaseModel)
|
||||
|
||||
result = FallbackResult(
|
||||
fallbacktype=FallbackType.ERROR,
|
||||
isfallback=True,
|
||||
modelroleconfig=None,
|
||||
basemodelconfig=None,
|
||||
)
|
||||
assert result.isfallback is True
|
||||
print("fallback-result-ok")
|
||||
|
||||
|
||||
def _test_auth_models() -> None:
|
||||
"""Auth model behaviour is unchanged after refactor."""
|
||||
from cleveragents.domain.models.auth.auth import (
|
||||
ApiError,
|
||||
ApiErrorType,
|
||||
AuthHeader,
|
||||
BillingError,
|
||||
ClientAccount,
|
||||
ClientAuth,
|
||||
TrialMessagesExceededError,
|
||||
TrialPlansExceededError,
|
||||
)
|
||||
|
||||
for cls in (
|
||||
AuthHeader,
|
||||
TrialPlansExceededError,
|
||||
TrialMessagesExceededError,
|
||||
BillingError,
|
||||
ApiError,
|
||||
ClientAccount,
|
||||
ClientAuth,
|
||||
):
|
||||
assert issubclass(cls, DomainBaseModel), f"{cls.__name__} not DomainBaseModel"
|
||||
|
||||
header = AuthHeader(token="tok", orgId="org-1", hash="abc")
|
||||
assert header.org_id == "org-1"
|
||||
|
||||
trial_plans = TrialPlansExceededError(maxPlans=5)
|
||||
assert trial_plans.max_plans == 5
|
||||
|
||||
trial_msgs = TrialMessagesExceededError(maxMessages=100)
|
||||
assert trial_msgs.max_messages == 100
|
||||
|
||||
billing = BillingError(hasBillingPermission=False, isTrial=True)
|
||||
assert billing.is_trial is True
|
||||
|
||||
api_err = ApiError(type_=ApiErrorType.AUTH, status=401, msg="Unauthorized")
|
||||
assert api_err.status == 401
|
||||
|
||||
account = ClientAccount(
|
||||
isCloud=True,
|
||||
host="app.cleverthis.com",
|
||||
email="user@example.com",
|
||||
userName="testuser",
|
||||
userId="uid-1",
|
||||
token="tok-1",
|
||||
isLocalMode=False,
|
||||
isTrial=False,
|
||||
)
|
||||
assert account.is_cloud is True
|
||||
|
||||
auth = ClientAuth(
|
||||
orgId="org-1",
|
||||
orgName="Test Org",
|
||||
orgIsTrial=False,
|
||||
integratedModelsMode=True,
|
||||
)
|
||||
assert auth.org_name == "Test Org"
|
||||
|
||||
print("auth-models-ok")
|
||||
|
||||
|
||||
def _test_plan_config_models() -> None:
|
||||
"""PlanConfig and ConfigSetting behaviour is unchanged after refactor."""
|
||||
from cleveragents.domain.models.planconfig.plan_config import (
|
||||
AutoModeType,
|
||||
ConfigSetting,
|
||||
PlanConfig,
|
||||
)
|
||||
|
||||
assert issubclass(PlanConfig, DomainBaseModel)
|
||||
assert issubclass(ConfigSetting, DomainBaseModel)
|
||||
|
||||
plan = PlanConfig(
|
||||
autoMode=AutoModeType.AUTO,
|
||||
editor="vim",
|
||||
editorCommand="vim",
|
||||
editorOpenManually=False,
|
||||
autoContinue=True,
|
||||
autoBuild=False,
|
||||
autoUpdateContext=True,
|
||||
autoContext=True,
|
||||
smartContext=True,
|
||||
autoApply=False,
|
||||
autoCommit=False,
|
||||
skipCommit=False,
|
||||
canExec=True,
|
||||
autoExec=False,
|
||||
autoDebug=False,
|
||||
autoDebugTries=3,
|
||||
autoRevertOnRewind=False,
|
||||
skipChangesMenu=False,
|
||||
)
|
||||
assert plan.editor == "vim"
|
||||
assert plan.auto_mode == AutoModeType.AUTO
|
||||
|
||||
setting = ConfigSetting(
|
||||
name="autoMode",
|
||||
desc="Auto mode setting",
|
||||
hascustomchoice=False,
|
||||
sortkey="a",
|
||||
)
|
||||
assert setting.name == "autoMode"
|
||||
|
||||
print("plan-config-models-ok")
|
||||
|
||||
|
||||
def _test_whitespace_stripping() -> None:
|
||||
"""str_strip_whitespace is active on all refactored models."""
|
||||
from cleveragents.domain.models.aimodelsproviders.ai_models_providers import (
|
||||
ModelProviderConfigSchema,
|
||||
)
|
||||
from cleveragents.domain.models.core.enums import ModelProvider
|
||||
|
||||
schema = ModelProviderConfigSchema(
|
||||
provider=ModelProvider.OPENAI,
|
||||
baseUrl=" https://api.openai.com/v1 ",
|
||||
)
|
||||
assert schema.base_url == "https://api.openai.com/v1", (
|
||||
f"Expected stripped URL, got: {schema.base_url!r}"
|
||||
)
|
||||
print("whitespace-stripping-ok")
|
||||
|
||||
|
||||
_COMMANDS: dict[str, object] = {
|
||||
"importable": _test_domain_base_model_importable,
|
||||
"config": _test_domain_base_model_config,
|
||||
"model-provider-option": _test_model_provider_option,
|
||||
"model-error": _test_model_error,
|
||||
"fallback-result": _test_fallback_result,
|
||||
"auth-models": _test_auth_models,
|
||||
"plan-config-models": _test_plan_config_models,
|
||||
"whitespace-stripping": _test_whitespace_stripping,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) < 2:
|
||||
raise SystemExit("Expected command argument")
|
||||
command = sys.argv[1]
|
||||
fn = _COMMANDS.get(command)
|
||||
if fn is None:
|
||||
raise SystemExit(f"Unknown helper command: {command!r}")
|
||||
fn() # type: ignore[operator]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,22 +1,15 @@
|
||||
"""Domain models for CleverAgents - auto-generated from Phase 0 stubs."""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import Field
|
||||
|
||||
from ..aimodelsproviders.ai_models_providers import ModelProviderConfigSchema
|
||||
from ..base import DomainBaseModel
|
||||
from ..core.enums import ModelPublisher
|
||||
|
||||
|
||||
class ModelProviderOption(BaseModel):
|
||||
class ModelProviderOption(DomainBaseModel):
|
||||
"""Data contract for ModelProviderOption."""
|
||||
|
||||
publishers: dict[str, dict[ModelPublisher, bool]] = Field(default_factory=dict)
|
||||
config: ModelProviderConfigSchema | None = Field(default=None)
|
||||
priority: int = Field(...)
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
@@ -1,39 +1,24 @@
|
||||
"""Domain models for CleverAgents - auto-generated from Phase 0 stubs."""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import Field
|
||||
|
||||
from ..aimodelsdatamodels import BaseModelConfig, ModelRoleConfig
|
||||
from ..base import DomainBaseModel
|
||||
from ..core.enums import FallbackType, ModelErrKind
|
||||
|
||||
|
||||
class ModelError(BaseModel):
|
||||
class ModelError(DomainBaseModel):
|
||||
"""Data contract for ModelError."""
|
||||
|
||||
kind: ModelErrKind = Field(...)
|
||||
retriable: bool = Field(...)
|
||||
retryafterseconds: int = Field(...)
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
|
||||
class FallbackResult(BaseModel):
|
||||
class FallbackResult(DomainBaseModel):
|
||||
"""Data contract for FallbackResult."""
|
||||
|
||||
modelroleconfig: ModelRoleConfig | None = Field(default=None)
|
||||
isfallback: bool = Field(...)
|
||||
fallbacktype: FallbackType = Field(...)
|
||||
basemodelconfig: BaseModelConfig | None = Field(default=None)
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"""Domain models for CleverAgents - auto-generated from Phase 0 stubs."""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import Field
|
||||
|
||||
from ..base import DomainBaseModel
|
||||
from ..core.enums import ModelProvider
|
||||
|
||||
|
||||
class ModelProviderExtraAuthVars(BaseModel):
|
||||
class ModelProviderExtraAuthVars(DomainBaseModel):
|
||||
"""Data contract for ModelProviderExtraAuthVars."""
|
||||
|
||||
var: str = Field(...)
|
||||
@@ -15,16 +16,8 @@ class ModelProviderExtraAuthVars(BaseModel):
|
||||
required: bool | None = Field(default=None)
|
||||
default: str | None = Field(default=None)
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
|
||||
class ModelProviderConfigSchema(BaseModel):
|
||||
class ModelProviderConfigSchema(DomainBaseModel):
|
||||
"""Data contract for ModelProviderConfigSchema."""
|
||||
|
||||
provider: ModelProvider = Field(...)
|
||||
@@ -38,11 +31,3 @@ class ModelProviderConfigSchema(BaseModel):
|
||||
extra_auth_vars: list[ModelProviderExtraAuthVars] | None = Field(
|
||||
alias="extraAuthVars", default=None
|
||||
)
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import Field
|
||||
|
||||
from ..base import DomainBaseModel
|
||||
|
||||
|
||||
class ApiErrorType(StrEnum):
|
||||
@@ -16,66 +18,34 @@ class ApiErrorType(StrEnum):
|
||||
NOT_FOUND = "not_found"
|
||||
|
||||
|
||||
class AuthHeader(BaseModel):
|
||||
class AuthHeader(DomainBaseModel):
|
||||
"""Data contract for AuthHeader."""
|
||||
|
||||
token: str = Field(...)
|
||||
org_id: str = Field(..., alias="orgId")
|
||||
hash: str = Field(...)
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
|
||||
class TrialPlansExceededError(BaseModel):
|
||||
class TrialPlansExceededError(DomainBaseModel):
|
||||
"""Data contract for TrialPlansExceededError."""
|
||||
|
||||
max_plans: int = Field(..., alias="maxPlans")
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
|
||||
class TrialMessagesExceededError(BaseModel):
|
||||
class TrialMessagesExceededError(DomainBaseModel):
|
||||
"""Data contract for TrialMessagesExceededError."""
|
||||
|
||||
max_messages: int = Field(..., alias="maxMessages")
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
|
||||
class BillingError(BaseModel):
|
||||
class BillingError(DomainBaseModel):
|
||||
"""Data contract for BillingError."""
|
||||
|
||||
has_billing_permission: bool = Field(..., alias="hasBillingPermission")
|
||||
is_trial: bool = Field(..., alias="isTrial")
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
|
||||
class ApiError(BaseModel):
|
||||
class ApiError(DomainBaseModel):
|
||||
"""Data contract for ApiError."""
|
||||
|
||||
type_: ApiErrorType = Field(...)
|
||||
@@ -89,16 +59,8 @@ class ApiError(BaseModel):
|
||||
)
|
||||
billing_error: BillingError | None = Field(alias="billingError", default=None)
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
|
||||
class ClientAccount(BaseModel):
|
||||
class ClientAccount(DomainBaseModel):
|
||||
"""Data contract for ClientAccount."""
|
||||
|
||||
is_cloud: bool = Field(..., alias="isCloud")
|
||||
@@ -110,27 +72,11 @@ class ClientAccount(BaseModel):
|
||||
is_local_mode: bool = Field(..., alias="isLocalMode")
|
||||
is_trial: bool = Field(..., alias="isTrial")
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
|
||||
class ClientAuth(BaseModel):
|
||||
class ClientAuth(DomainBaseModel):
|
||||
"""Data contract for ClientAuth."""
|
||||
|
||||
org_id: str = Field(..., alias="orgId")
|
||||
org_name: str = Field(..., alias="orgName")
|
||||
org_is_trial: bool = Field(..., alias="orgIsTrial")
|
||||
integrated_models_mode: bool = Field(..., alias="integratedModelsMode")
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Shared base Pydantic model for the domain layer.
|
||||
|
||||
All domain models that share the standard configuration should inherit from
|
||||
``DomainBaseModel`` instead of ``pydantic.BaseModel`` directly. This ensures
|
||||
the shared ``model_config`` is defined in exactly one place and that any future
|
||||
change to the configuration is automatically propagated to every consumer.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
__all__ = ["DomainBaseModel"]
|
||||
|
||||
|
||||
class DomainBaseModel(BaseModel):
|
||||
"""Base class for all standard domain-layer Pydantic models.
|
||||
|
||||
Provides the shared ``model_config`` that was previously duplicated across
|
||||
every model file in the domain layer:
|
||||
|
||||
- ``str_strip_whitespace``: leading/trailing whitespace is stripped from
|
||||
all ``str`` fields on assignment and validation.
|
||||
- ``validate_assignment``: field assignments after construction are
|
||||
validated just like constructor arguments.
|
||||
- ``arbitrary_types_allowed``: disabled — all field types must be
|
||||
Pydantic-compatible to keep the domain layer clean.
|
||||
- ``populate_by_name``: models can be constructed using either the Python
|
||||
field name or the JSON alias.
|
||||
- ``use_enum_values``: enum fields are stored and serialised as their
|
||||
underlying primitive values rather than as enum instances.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import Field
|
||||
|
||||
from ..base import DomainBaseModel
|
||||
|
||||
|
||||
class AutoModeType(StrEnum):
|
||||
@@ -13,7 +15,7 @@ class AutoModeType(StrEnum):
|
||||
GUIDED = "guided"
|
||||
|
||||
|
||||
class PlanConfig(BaseModel):
|
||||
class PlanConfig(DomainBaseModel):
|
||||
"""Data contract for PlanConfig."""
|
||||
|
||||
auto_mode: AutoModeType = Field(..., alias="autoMode")
|
||||
@@ -36,16 +38,8 @@ class PlanConfig(BaseModel):
|
||||
auto_revert_on_rewind: bool = Field(..., alias="autoRevertOnRewind")
|
||||
skip_changes_menu: bool = Field(..., alias="skipChangesMenu")
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
|
||||
class ConfigSetting(BaseModel):
|
||||
class ConfigSetting(DomainBaseModel):
|
||||
"""Data contract for ConfigSetting."""
|
||||
|
||||
name: str = Field(...)
|
||||
@@ -53,11 +47,3 @@ class ConfigSetting(BaseModel):
|
||||
choices: list[str] | None = Field(default=None)
|
||||
hascustomchoice: bool = Field(...)
|
||||
sortkey: str = Field(...)
|
||||
|
||||
model_config = ConfigDict(
|
||||
str_strip_whitespace=True,
|
||||
validate_assignment=True,
|
||||
arbitrary_types_allowed=False,
|
||||
populate_by_name=True,
|
||||
use_enum_values=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user