mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-04-19 00:45:22 +00:00
REST: Allow setting single config value
* /v1/config/{path} supports setting single config value (post body). Lists are supported as well by using the index: - general/latitude (value: 55.55) - optimize/ev_available_charge_rates_percent/0 (value: 42) Whole tree can be overriden as well (no merge): - optimize/ev_available_charge_rates_percent (value: [42, 43, 44] * ConfigEOS: Add set_config_value, get_config_value
This commit is contained in:
parent
1bb74ed836
commit
94618f5f66
@ -257,6 +257,70 @@ Returns:
|
||||
|
||||
---
|
||||
|
||||
## GET /v1/config/{path}
|
||||
|
||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_config_get_key_v1_config__path__get), [eos](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_config_get_key_v1_config__path__get)
|
||||
|
||||
Fastapi Config Get Key
|
||||
|
||||
```
|
||||
Get the value of a nested key or index in the config model.
|
||||
|
||||
Args:
|
||||
path (str): The nested path to the key (e.g., "general/latitude" or "optimize/nested_list/0").
|
||||
|
||||
Returns:
|
||||
value (Any): The value of the selected nested key.
|
||||
```
|
||||
|
||||
**Parameters**:
|
||||
|
||||
- `path` (path, required): The nested path to the configuration key (e.g., general/latitude).
|
||||
|
||||
**Responses**:
|
||||
|
||||
- **200**: Successful Response
|
||||
|
||||
- **422**: Validation Error
|
||||
|
||||
---
|
||||
|
||||
## PUT /v1/config/{path}
|
||||
|
||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_config_put_key_v1_config__path__put), [eos](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_config_put_key_v1_config__path__put)
|
||||
|
||||
Fastapi Config Put Key
|
||||
|
||||
```
|
||||
Update a nested key or index in the config model.
|
||||
|
||||
Args:
|
||||
path (str): The nested path to the key (e.g., "general/latitude" or "optimize/nested_list/0").
|
||||
value (Any): The new value to assign to the key or index at path.
|
||||
|
||||
Returns:
|
||||
configuration (ConfigEOS): The current configuration after the update.
|
||||
```
|
||||
|
||||
**Parameters**:
|
||||
|
||||
- `path` (path, required): The nested path to the configuration key (e.g., general/latitude).
|
||||
|
||||
**Request Body**:
|
||||
|
||||
- `application/json`: {
|
||||
"description": "The value to assign to the specified configuration path.",
|
||||
"title": "Value"
|
||||
}
|
||||
|
||||
**Responses**:
|
||||
|
||||
- **200**: Successful Response
|
||||
|
||||
- **422**: Validation Error
|
||||
|
||||
---
|
||||
|
||||
## PUT /v1/measurement/data
|
||||
|
||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_measurement_data_put_v1_measurement_data_put), [eos](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_measurement_data_put_v1_measurement_data_put)
|
||||
|
577
openapi.json
577
openapi.json
@ -106,246 +106,6 @@
|
||||
"title": "BaseBatteryParameters",
|
||||
"type": "object"
|
||||
},
|
||||
"GeneralSettings-Input": {
|
||||
"description": "Settings for common configuration.\n\nGeneral configuration to set directories of cache and output files and system location (latitude\nand longitude).\nValidators ensure each parameter is within a specified range. A computed property, `timezone`,\ndetermines the time zone based on latitude and longitude.\n\nAttributes:\n latitude (Optional[float]): Latitude in degrees, must be between -90 and 90.\n longitude (Optional[float]): Longitude in degrees, must be between -180 and 180.\n\nProperties:\n timezone (Optional[str]): Computed time zone string based on the specified latitude\n and longitude.\n\nValidators:\n validate_latitude (float): Ensures `latitude` is within the range -90 to 90.\n validate_longitude (float): Ensures `longitude` is within the range -180 to 180.",
|
||||
"properties": {
|
||||
"data_cache_subpath": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "cache",
|
||||
"description": "Sub-path for the EOS cache data directory.",
|
||||
"title": "Data Cache Subpath"
|
||||
},
|
||||
"data_folder_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path to EOS data directory.",
|
||||
"examples": [
|
||||
null,
|
||||
"/home/eos/data"
|
||||
],
|
||||
"title": "Data Folder Path"
|
||||
},
|
||||
"data_output_subpath": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "output",
|
||||
"description": "Sub-path for the EOS output data directory.",
|
||||
"title": "Data Output Subpath"
|
||||
},
|
||||
"latitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 90.0,
|
||||
"minimum": -90.0,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": 52.52,
|
||||
"description": "Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (\u00b0)",
|
||||
"title": "Latitude"
|
||||
},
|
||||
"longitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 180.0,
|
||||
"minimum": -180.0,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": 13.405,
|
||||
"description": "Longitude in decimal degrees, within -180 to 180 (\u00b0)",
|
||||
"title": "Longitude"
|
||||
}
|
||||
},
|
||||
"title": "GeneralSettings",
|
||||
"type": "object"
|
||||
},
|
||||
"GeneralSettings-Output": {
|
||||
"description": "Settings for common configuration.\n\nGeneral configuration to set directories of cache and output files and system location (latitude\nand longitude).\nValidators ensure each parameter is within a specified range. A computed property, `timezone`,\ndetermines the time zone based on latitude and longitude.\n\nAttributes:\n latitude (Optional[float]): Latitude in degrees, must be between -90 and 90.\n longitude (Optional[float]): Longitude in degrees, must be between -180 and 180.\n\nProperties:\n timezone (Optional[str]): Computed time zone string based on the specified latitude\n and longitude.\n\nValidators:\n validate_latitude (float): Ensures `latitude` is within the range -90 to 90.\n validate_longitude (float): Ensures `longitude` is within the range -180 to 180.",
|
||||
"properties": {
|
||||
"config_file_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path to EOS configuration file.",
|
||||
"readOnly": true,
|
||||
"title": "Config File Path"
|
||||
},
|
||||
"config_folder_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path to EOS configuration directory.",
|
||||
"readOnly": true,
|
||||
"title": "Config Folder Path"
|
||||
},
|
||||
"data_cache_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Compute data_cache_path based on data_folder_path.",
|
||||
"readOnly": true,
|
||||
"title": "Data Cache Path"
|
||||
},
|
||||
"data_cache_subpath": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "cache",
|
||||
"description": "Sub-path for the EOS cache data directory.",
|
||||
"title": "Data Cache Subpath"
|
||||
},
|
||||
"data_folder_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path to EOS data directory.",
|
||||
"examples": [
|
||||
null,
|
||||
"/home/eos/data"
|
||||
],
|
||||
"title": "Data Folder Path"
|
||||
},
|
||||
"data_output_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Compute data_output_path based on data_folder_path.",
|
||||
"readOnly": true,
|
||||
"title": "Data Output Path"
|
||||
},
|
||||
"data_output_subpath": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "output",
|
||||
"description": "Sub-path for the EOS output data directory.",
|
||||
"title": "Data Output Subpath"
|
||||
},
|
||||
"latitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 90.0,
|
||||
"minimum": -90.0,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": 52.52,
|
||||
"description": "Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (\u00b0)",
|
||||
"title": "Latitude"
|
||||
},
|
||||
"longitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 180.0,
|
||||
"minimum": -180.0,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": 13.405,
|
||||
"description": "Longitude in decimal degrees, within -180 to 180 (\u00b0)",
|
||||
"title": "Longitude"
|
||||
},
|
||||
"timezone": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Compute timezone based on latitude and longitude.",
|
||||
"readOnly": true,
|
||||
"title": "Timezone"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"timezone",
|
||||
"data_output_path",
|
||||
"data_cache_path",
|
||||
"config_folder_path",
|
||||
"config_file_path"
|
||||
],
|
||||
"title": "GeneralSettings",
|
||||
"type": "object"
|
||||
},
|
||||
"ConfigEOS": {
|
||||
"additionalProperties": false,
|
||||
"description": "Singleton configuration handler for the EOS application.\n\nConfigEOS extends `SettingsEOS` with support for default configuration paths and automatic\ninitialization.\n\n`ConfigEOS` ensures that only one instance of the class is created throughout the application,\nallowing consistent access to EOS configuration settings. This singleton instance loads\nconfiguration data from a predefined set of directories or creates a default configuration if\nnone is found.\n\nInitialization Process:\n - Upon instantiation, the singleton instance attempts to load a configuration file in this order:\n 1. The directory specified by the `EOS_CONFIG_DIR` environment variable\n 2. The directory specified by the `EOS_DIR` environment variable.\n 3. A platform specific default directory for EOS.\n 4. The current working directory.\n - The first available configuration file found in these directories is loaded.\n - If no configuration file is found, a default configuration file is created in the platform\n specific default directory, and default settings are loaded into it.\n\nAttributes from the loaded configuration are accessible directly as instance attributes of\n`ConfigEOS`, providing a centralized, shared configuration object for EOS.\n\nSingleton Behavior:\n - This class uses the `SingletonMixin` to ensure that all requests for `ConfigEOS` return\n the same instance, which contains the most up-to-date configuration. Modifying the configuration\n in one part of the application reflects across all references to this class.\n\nAttributes:\n config_folder_path (Optional[Path]): Path to the configuration directory.\n config_file_path (Optional[Path]): Path to the configuration file.\n\nRaises:\n FileNotFoundError: If no configuration file is found, and creating a default configuration fails.\n\nExample:\n To initialize and access configuration attributes (only one instance is created):\n ```python\n config_eos = ConfigEOS() # Always returns the same instance\n print(config_eos.prediction.hours) # Access a setting from the loaded configuration\n ```",
|
||||
@ -877,6 +637,246 @@
|
||||
"title": "ForecastResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"GeneralSettings-Input": {
|
||||
"description": "Settings for common configuration.\n\nGeneral configuration to set directories of cache and output files and system location (latitude\nand longitude).\nValidators ensure each parameter is within a specified range. A computed property, `timezone`,\ndetermines the time zone based on latitude and longitude.\n\nAttributes:\n latitude (Optional[float]): Latitude in degrees, must be between -90 and 90.\n longitude (Optional[float]): Longitude in degrees, must be between -180 and 180.\n\nProperties:\n timezone (Optional[str]): Computed time zone string based on the specified latitude\n and longitude.\n\nValidators:\n validate_latitude (float): Ensures `latitude` is within the range -90 to 90.\n validate_longitude (float): Ensures `longitude` is within the range -180 to 180.",
|
||||
"properties": {
|
||||
"data_cache_subpath": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "cache",
|
||||
"description": "Sub-path for the EOS cache data directory.",
|
||||
"title": "Data Cache Subpath"
|
||||
},
|
||||
"data_folder_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path to EOS data directory.",
|
||||
"examples": [
|
||||
null,
|
||||
"/home/eos/data"
|
||||
],
|
||||
"title": "Data Folder Path"
|
||||
},
|
||||
"data_output_subpath": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "output",
|
||||
"description": "Sub-path for the EOS output data directory.",
|
||||
"title": "Data Output Subpath"
|
||||
},
|
||||
"latitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 90.0,
|
||||
"minimum": -90.0,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": 52.52,
|
||||
"description": "Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (\u00b0)",
|
||||
"title": "Latitude"
|
||||
},
|
||||
"longitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 180.0,
|
||||
"minimum": -180.0,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": 13.405,
|
||||
"description": "Longitude in decimal degrees, within -180 to 180 (\u00b0)",
|
||||
"title": "Longitude"
|
||||
}
|
||||
},
|
||||
"title": "GeneralSettings",
|
||||
"type": "object"
|
||||
},
|
||||
"GeneralSettings-Output": {
|
||||
"description": "Settings for common configuration.\n\nGeneral configuration to set directories of cache and output files and system location (latitude\nand longitude).\nValidators ensure each parameter is within a specified range. A computed property, `timezone`,\ndetermines the time zone based on latitude and longitude.\n\nAttributes:\n latitude (Optional[float]): Latitude in degrees, must be between -90 and 90.\n longitude (Optional[float]): Longitude in degrees, must be between -180 and 180.\n\nProperties:\n timezone (Optional[str]): Computed time zone string based on the specified latitude\n and longitude.\n\nValidators:\n validate_latitude (float): Ensures `latitude` is within the range -90 to 90.\n validate_longitude (float): Ensures `longitude` is within the range -180 to 180.",
|
||||
"properties": {
|
||||
"config_file_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path to EOS configuration file.",
|
||||
"readOnly": true,
|
||||
"title": "Config File Path"
|
||||
},
|
||||
"config_folder_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path to EOS configuration directory.",
|
||||
"readOnly": true,
|
||||
"title": "Config Folder Path"
|
||||
},
|
||||
"data_cache_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Compute data_cache_path based on data_folder_path.",
|
||||
"readOnly": true,
|
||||
"title": "Data Cache Path"
|
||||
},
|
||||
"data_cache_subpath": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "cache",
|
||||
"description": "Sub-path for the EOS cache data directory.",
|
||||
"title": "Data Cache Subpath"
|
||||
},
|
||||
"data_folder_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path to EOS data directory.",
|
||||
"examples": [
|
||||
null,
|
||||
"/home/eos/data"
|
||||
],
|
||||
"title": "Data Folder Path"
|
||||
},
|
||||
"data_output_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Compute data_output_path based on data_folder_path.",
|
||||
"readOnly": true,
|
||||
"title": "Data Output Path"
|
||||
},
|
||||
"data_output_subpath": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "output",
|
||||
"description": "Sub-path for the EOS output data directory.",
|
||||
"title": "Data Output Subpath"
|
||||
},
|
||||
"latitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 90.0,
|
||||
"minimum": -90.0,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": 52.52,
|
||||
"description": "Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (\u00b0)",
|
||||
"title": "Latitude"
|
||||
},
|
||||
"longitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 180.0,
|
||||
"minimum": -180.0,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": 13.405,
|
||||
"description": "Longitude in decimal degrees, within -180 to 180 (\u00b0)",
|
||||
"title": "Longitude"
|
||||
},
|
||||
"timezone": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Compute timezone based on latitude and longitude.",
|
||||
"readOnly": true,
|
||||
"title": "Timezone"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"timezone",
|
||||
"data_output_path",
|
||||
"data_cache_path",
|
||||
"config_folder_path",
|
||||
"config_file_path"
|
||||
],
|
||||
"title": "GeneralSettings",
|
||||
"type": "object"
|
||||
},
|
||||
"GesamtlastRequest": {
|
||||
"properties": {
|
||||
"hours": {
|
||||
@ -3166,6 +3166,103 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/config/{path}": {
|
||||
"get": {
|
||||
"description": "Get the value of a nested key or index in the config model.\n\nArgs:\n path (str): The nested path to the key (e.g., \"general/latitude\" or \"optimize/nested_list/0\").\n\nReturns:\n value (Any): The value of the selected nested key.",
|
||||
"operationId": "fastapi_config_get_key_v1_config__path__get",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The nested path to the configuration key (e.g., general/latitude).",
|
||||
"in": "path",
|
||||
"name": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "The nested path to the configuration key (e.g., general/latitude).",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Fastapi Config Get Key",
|
||||
"tags": [
|
||||
"config"
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"description": "Update a nested key or index in the config model.\n\nArgs:\n path (str): The nested path to the key (e.g., \"general/latitude\" or \"optimize/nested_list/0\").\n value (Any): The new value to assign to the key or index at path.\n\nReturns:\n configuration (ConfigEOS): The current configuration after the update.",
|
||||
"operationId": "fastapi_config_put_key_v1_config__path__put",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The nested path to the configuration key (e.g., general/latitude).",
|
||||
"in": "path",
|
||||
"name": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "The nested path to the configuration key (e.g., general/latitude).",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"description": "The value to assign to the specified configuration path.",
|
||||
"title": "Value"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConfigEOS"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Fastapi Config Put Key",
|
||||
"tags": [
|
||||
"config"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/measurement/data": {
|
||||
"put": {
|
||||
"description": "Merge the measurement data given as datetime data into EOS measurements.",
|
||||
|
@ -30,7 +30,7 @@ from akkudoktoreos.core.coreabc import SingletonMixin
|
||||
from akkudoktoreos.core.decorators import classproperty
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
from akkudoktoreos.core.logsettings import LoggingCommonSettings
|
||||
from akkudoktoreos.core.pydantic import merge_models
|
||||
from akkudoktoreos.core.pydantic import access_nested_value, merge_models
|
||||
from akkudoktoreos.devices.settings import DevicesCommonSettings
|
||||
from akkudoktoreos.measurement.measurement import MeasurementCommonSettings
|
||||
from akkudoktoreos.optimization.optimization import OptimizationCommonSettings
|
||||
@ -379,6 +379,32 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
"""
|
||||
self._setup()
|
||||
|
||||
def set_config_value(self, path: str, value: Any) -> None:
|
||||
"""Set a configuration value based on the provided path.
|
||||
|
||||
Supports string paths (with '/' separators) or sequence paths (list/tuple).
|
||||
Trims leading and trailing '/' from string paths.
|
||||
|
||||
Args:
|
||||
path (str): The path to the configuration key (e.g., "key1/key2/key3" or key1/key2/0).
|
||||
value (Any]): The value to set.
|
||||
"""
|
||||
access_nested_value(self, path, True, value)
|
||||
|
||||
def get_config_value(self, path: str) -> Any:
|
||||
"""Get a configuration value based on the provided path.
|
||||
|
||||
Supports string paths (with '/' separators) or sequence paths (list/tuple).
|
||||
Trims leading and trailing '/' from string paths.
|
||||
|
||||
Args:
|
||||
path (str): The path to the configuration key (e.g., "key1/key2/key3" or key1/key2/0).
|
||||
|
||||
Returns:
|
||||
Any: The retrieved value.
|
||||
"""
|
||||
return access_nested_value(self, path, False)
|
||||
|
||||
def _create_initial_config_file(self) -> None:
|
||||
if self.general.config_file_path and not self.general.config_file_path.exists():
|
||||
self.general.config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
@ -51,6 +51,70 @@ def merge_models(source: BaseModel, update_dict: dict[str, Any]) -> dict[str, An
|
||||
return merged_dict
|
||||
|
||||
|
||||
def access_nested_value(
|
||||
model: BaseModel, path: str, setter: bool, value: Optional[Any] = None
|
||||
) -> Any:
|
||||
"""Get or set a nested model value based on the provided path.
|
||||
|
||||
Supports string paths (with '/' separators) or sequence paths (list/tuple).
|
||||
Trims leading and trailing '/' from string paths.
|
||||
|
||||
Args:
|
||||
model (BaseModel): The model object for partial assignment.
|
||||
path (str): The path to the model key (e.g., "key1/key2/key3" or key1/key2/0).
|
||||
setter (bool): True to set value at path, False to return value at path.
|
||||
value (Optional[Any]): The value to set.
|
||||
|
||||
Returns:
|
||||
Any: The retrieved value if acting as a getter, or None if setting a value.
|
||||
"""
|
||||
path_elements = path.strip("/").split("/")
|
||||
|
||||
cfg: Any = model
|
||||
parent: BaseModel = model
|
||||
model_key: str = ""
|
||||
|
||||
for i, key in enumerate(path_elements):
|
||||
is_final_key = i == len(path_elements) - 1
|
||||
|
||||
if isinstance(cfg, list):
|
||||
try:
|
||||
idx = int(key)
|
||||
if is_final_key:
|
||||
if not setter: # Getter
|
||||
return cfg[idx]
|
||||
else: # Setter
|
||||
new_list = list(cfg)
|
||||
new_list[idx] = value
|
||||
# Trigger validation
|
||||
setattr(parent, model_key, new_list)
|
||||
else:
|
||||
cfg = cfg[idx]
|
||||
except ValidationError as e:
|
||||
raise ValueError(f"Error updating model: {e}") from e
|
||||
except (ValueError, IndexError) as e:
|
||||
raise IndexError(f"Invalid list index at {path}: {key}") from e
|
||||
|
||||
elif isinstance(cfg, BaseModel):
|
||||
parent = cfg
|
||||
model_key = key
|
||||
if is_final_key:
|
||||
if not setter: # Getter
|
||||
return getattr(cfg, key)
|
||||
else: # Setter
|
||||
try:
|
||||
# Verification also if nested value is provided opposed to just setattr
|
||||
# Will merge partial assignment
|
||||
cfg = cfg.__pydantic_validator__.validate_assignment(cfg, key, value)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error updating model: {e}") from e
|
||||
else:
|
||||
cfg = getattr(cfg, key)
|
||||
|
||||
else:
|
||||
raise KeyError(f"Key '{key}' not found in model.")
|
||||
|
||||
|
||||
class PydanticTypeAdapterDateTime(TypeAdapter[pendulum.DateTime]):
|
||||
"""Custom type adapter for Pendulum DateTime fields."""
|
||||
|
||||
|
@ -10,7 +10,9 @@ from typing import Annotated, Any, AsyncGenerator, Dict, List, Optional, Union
|
||||
|
||||
import httpx
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Query, Request
|
||||
from fastapi import Body, FastAPI
|
||||
from fastapi import Path as FastapiPath
|
||||
from fastapi import Query, Request
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse, Response
|
||||
|
||||
@ -302,6 +304,58 @@ def fastapi_config_put(settings: SettingsEOS) -> ConfigEOS:
|
||||
return config_eos
|
||||
|
||||
|
||||
@app.put("/v1/config/{path:path}", tags=["config"])
|
||||
def fastapi_config_put_key(
|
||||
path: str = FastapiPath(
|
||||
..., description="The nested path to the configuration key (e.g., general/latitude)."
|
||||
),
|
||||
value: Any = Body(..., description="The value to assign to the specified configuration path."),
|
||||
) -> ConfigEOS:
|
||||
"""Update a nested key or index in the config model.
|
||||
|
||||
Args:
|
||||
path (str): The nested path to the key (e.g., "general/latitude" or "optimize/nested_list/0").
|
||||
value (Any): The new value to assign to the key or index at path.
|
||||
|
||||
Returns:
|
||||
configuration (ConfigEOS): The current configuration after the update.
|
||||
"""
|
||||
try:
|
||||
config_eos.set_config_value(path, value)
|
||||
except IndexError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except KeyError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
return config_eos
|
||||
|
||||
|
||||
@app.get("/v1/config/{path:path}", tags=["config"])
|
||||
def fastapi_config_get_key(
|
||||
path: str = FastapiPath(
|
||||
..., description="The nested path to the configuration key (e.g., general/latitude)."
|
||||
),
|
||||
) -> Response:
|
||||
"""Get the value of a nested key or index in the config model.
|
||||
|
||||
Args:
|
||||
path (str): The nested path to the key (e.g., "general/latitude" or "optimize/nested_list/0").
|
||||
|
||||
Returns:
|
||||
value (Any): The value of the selected nested key.
|
||||
"""
|
||||
try:
|
||||
return config_eos.get_config_value(path)
|
||||
except IndexError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except KeyError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/v1/measurement/keys", tags=["measurement"])
|
||||
def fastapi_measurement_keys_get() -> list[str]:
|
||||
"""Get a list of available measurement keys."""
|
||||
|
@ -212,3 +212,165 @@ def test_config_common_settings_timezone_none_when_coordinates_missing():
|
||||
assert config_no_latitude.timezone is None
|
||||
assert config_no_longitude.timezone is None
|
||||
assert config_no_coords.timezone is None
|
||||
|
||||
|
||||
# Test partial assignments and possible side effects
|
||||
@pytest.mark.parametrize(
|
||||
"path, value, expected, exception",
|
||||
[
|
||||
# Correct value assignment
|
||||
(
|
||||
"general/latitude",
|
||||
42.0,
|
||||
[("general.latitude", 42.0), ("general.longitude", 13.405)],
|
||||
None,
|
||||
),
|
||||
# Correct value assignment (trailing /)
|
||||
(
|
||||
"general/latitude/",
|
||||
41,
|
||||
[("general.latitude", 41.0), ("general.longitude", 13.405)],
|
||||
None,
|
||||
),
|
||||
# Correct value assignment (cast)
|
||||
(
|
||||
"general/latitude",
|
||||
"43.0",
|
||||
[("general.latitude", 43.0), ("general.longitude", 13.405)],
|
||||
None,
|
||||
),
|
||||
# Invalid value assignment (constraint)
|
||||
(
|
||||
"general/latitude",
|
||||
91.0,
|
||||
[("general.latitude", 52.52), ("general.longitude", 13.405)],
|
||||
ValueError,
|
||||
),
|
||||
# Invalid value assignment (type)
|
||||
(
|
||||
"general/latitude",
|
||||
"test",
|
||||
[("general.latitude", 52.52), ("general.longitude", 13.405)],
|
||||
ValueError,
|
||||
),
|
||||
# Invalid path
|
||||
(
|
||||
"general/latitude/test",
|
||||
"",
|
||||
[("general.latitude", 52.52), ("general.longitude", 13.405)],
|
||||
KeyError,
|
||||
),
|
||||
# Correct value nested assignment
|
||||
(
|
||||
"general",
|
||||
{"latitude": 22},
|
||||
[("general.latitude", 22.0), ("general.longitude", 13.405)],
|
||||
None,
|
||||
),
|
||||
# Invalid value nested assignment
|
||||
(
|
||||
"general",
|
||||
{"latitude": "test"},
|
||||
[("general.latitude", 52.52), ("general.longitude", 13.405)],
|
||||
ValueError,
|
||||
),
|
||||
# Correct value for list
|
||||
(
|
||||
"optimization/ev_available_charge_rates_percent/0",
|
||||
0.1,
|
||||
[
|
||||
(
|
||||
"optimization.ev_available_charge_rates_percent",
|
||||
[0.1, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0],
|
||||
)
|
||||
],
|
||||
None,
|
||||
),
|
||||
# Invalid value for list
|
||||
(
|
||||
"optimization/ev_available_charge_rates_percent/0",
|
||||
"invalid",
|
||||
[
|
||||
(
|
||||
"optimization.ev_available_charge_rates_percent",
|
||||
[0.0, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0],
|
||||
)
|
||||
],
|
||||
ValueError,
|
||||
),
|
||||
# Invalid index (out of bound)
|
||||
(
|
||||
"optimization/ev_available_charge_rates_percent/10",
|
||||
0,
|
||||
[
|
||||
(
|
||||
"optimization.ev_available_charge_rates_percent",
|
||||
[0.0, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0],
|
||||
)
|
||||
],
|
||||
IndexError,
|
||||
),
|
||||
# Invalid index (no number)
|
||||
(
|
||||
"optimization/ev_available_charge_rates_percent/test",
|
||||
0,
|
||||
[
|
||||
(
|
||||
"optimization.ev_available_charge_rates_percent",
|
||||
[0.0, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0],
|
||||
)
|
||||
],
|
||||
IndexError,
|
||||
),
|
||||
# Unset value (set None)
|
||||
(
|
||||
"optimization/ev_available_charge_rates_percent",
|
||||
None,
|
||||
[
|
||||
(
|
||||
"optimization.ev_available_charge_rates_percent",
|
||||
None,
|
||||
)
|
||||
],
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_set_nested_key(path, value, expected, exception, config_eos):
|
||||
if not exception:
|
||||
config_eos.set_config_value(path, value)
|
||||
for expected_path, expected_value in expected:
|
||||
assert eval(f"config_eos.{expected_path}") == expected_value
|
||||
else:
|
||||
with pytest.raises(exception):
|
||||
config_eos.set_config_value(path, value)
|
||||
for expected_path, expected_value in expected:
|
||||
assert eval(f"config_eos.{expected_path}") == expected_value
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path, expected_value, exception",
|
||||
[
|
||||
("general/latitude", 52.52, None),
|
||||
("general/latitude/", 52.52, None),
|
||||
("general/latitude/test", None, KeyError),
|
||||
(
|
||||
"optimization/ev_available_charge_rates_percent/1",
|
||||
0.375,
|
||||
None,
|
||||
),
|
||||
("optimization/ev_available_charge_rates_percent/10", 0, IndexError),
|
||||
("optimization/ev_available_charge_rates_percent/test", 0, IndexError),
|
||||
(
|
||||
"optimization/ev_available_charge_rates_percent",
|
||||
[0.0, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0],
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_nested_key(path, expected_value, exception, config_eos):
|
||||
if not exception:
|
||||
assert config_eos.get_config_value(path) == expected_value
|
||||
else:
|
||||
with pytest.raises(exception):
|
||||
config_eos.get_config_value(path)
|
||||
|
Loading…
x
Reference in New Issue
Block a user