# upgrade

Python API: `luxonis_train.upgrade`

Upgrade of an old config and of the installed package.

[upgrade_config](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/upgrade.md)
migrates a config from an older release to the schema of the installed release.
[Config.get_config](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/config/config.md)
calls it for each config file or dictionary that it loads. The `luxonis_train upgrade config` command writes the result to a file.
[upgrade_installation](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/upgrade.md)
upgrades the package with `pip`.

## Classes

### NestedDict

A wrapper that addresses a nested dictionary with dotted keys.

The key `"trainer.optimizer.name"` stands for `config["trainer"]["optimizer"]["name"]`. A read of a missing key returns `None`,
and a write creates the missing dictionaries. The wrapper does not copy the dictionary, so each change goes into the wrapped
dictionary.

> **Example**
> ```pycon
>>> from luxonis_train.upgrade import NestedDict
>>> config = {"trainer": {"epochs": 10}}
>>> cfg = NestedDict(config)
>>> cfg["trainer.epochs"], cfg["trainer.batch_size"]
(10, None)
>>> cfg["model.name"] = "detector"
>>> config
{'trainer': {'epochs': 10}, 'model': {'name': 'detector'}}
```

#### Methods

##### get

```python
def get(key: str, default: Any = None) -> Any:
```

Return the value under a dotted key, or a default value.

Parameters

 * `key` (`str`): A dotted key, for example `"model.nodes"`.
 * `default` (`Any`): The value to return when `key` does not exist.

Returns

 * `Any`: The value under `key`, or `default` when `key` does not exist. A stored `None` gives `None`.

##### log_change

```python
def log_change(old_field: str, new_field: str):
```

Log at the `INFO` level that a config field has a new key.

Parameters

 * `old_field` (`str`): The old dotted key.
 * `new_field` (`str`): The new dotted key.

##### pop

```python
def pop(key: str, default: Any = ...) -> Any:
```

Remove a dotted key and return its value.

The method removes only the last part of `key`. The parent dictionaries stay, also when they become empty.

> **Example**
> ```pycon
>>> from luxonis_train.upgrade import NestedDict
>>> config = {"exporter": {"output_names": ["boxes"]}}
>>> cfg = NestedDict(config)
>>> cfg.pop("exporter.output_names")
['boxes']
>>> config
{'exporter': {}}
>>> cfg.pop("exporter.output_names", None) is None
True
```

Parameters

 * `key` (`str`): A dotted key, for example `"exporter.output_names"`.
 * `default` (`Any`): The value to return when `key` does not exist. The default `...` means that there is no default value.

Returns

 * `Any`: The removed value, or `default` when `key` does not exist.

Raises

 * `KeyError`: When `key` does not exist and `default` is `...`.

##### replace

```python
def replace(old_key: str, new_key: str, value: ParamValue | EllipsisType | None = ...):
```

Move a value to a new dotted key and log the move.

The method does nothing when `old_key` does not exist. Otherwise, it removes `old_key` as
[pop](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/upgrade.md)
does and sets `new_key` as `self[new_key] = value` does. Then it calls
[log_change](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-train/luxonis-train-api-reference/upgrade.md).

Parameters

 * `old_key` (`str`): The dotted key to remove.
 * `new_key` (`str`): The dotted key to set.
 * `value` (`ParamValue | EllipsisType | None`): The value for `new_key`. The default `...` keeps the old value. Any other value,
   `None` included, replaces it.

##### update

```python
def update(key: str, value: Any):
```

Set the value under a dotted key and log the change.

The method logs a message at the `INFO` level. For a missing `key`, the message says that the field is new. Otherwise, it shows
the old value and the new value. Then the method sets the value as `self[key] = value` does.

Parameters

 * `key` (`str`): A dotted key, for example `"version"`.
 * `value` (`Any`): The new value.

## Functions

### get_latest_version

```python
def get_latest_version() -> Version | None:
```

Get the version of the latest `luxonis-train` release on PyPI.

The function reads `info.version` from the PyPI JSON API. The request has a timeout of 5 seconds.

Returns

 * `Version | None`: The latest version. It is `None` when the request fails or when the response status is not `200`. It is also
   `None` when the response body is not JSON with a valid `info.version`.

### upgrade_config

```python
def upgrade_config(config: PathType | Params) -> Params:
```

Migrate a config to the schema of the installed release.

The function reads a file as JSON when its suffix is `.json`, and as YAML otherwise. It does not write the file back. It does not
copy a dictionary: it changes the dictionary in place and returns it.

A config without `version` counts as version `0.3.0`. The function always removes the deprecated `config_version` field. When
`version` is the installed version or newer, the function makes no other change. Otherwise, it does these steps:

 * It renames these fields: * `trainer.use_rich_progress_bar` to `rich_logging`.
    * `preprocessing.train_rgb` to `preprocessing.color_space`. A true value gives `"RGB"`, and a false value gives `"BGR"`.
    * `model.predefined_model.params.variant` to `model.predefined_model.variant`.
    * `tuner.storage.storage_type` to `tuner.storage.backend`. `"local"` gives `"sqlite"`, and any other value gives
      `"postgresql"`.
 * It removes a `tuner` field with the value `None`.
 * In each node of `model.nodes`, it moves `params.variant` to `variant`. For a `FOMOHead`, it renames `params.num_conv_layers` to
   `params.n_conv_layers`. It removes `params.download_weights`, and when that value is true, it sets `params.weights` to
   `"download"`.
 * It moves `exporter.output_names` to the `params.export_output_names` of the head, when the model has exactly one head.
   Otherwise, it logs an error and drops the names.
 * It moves each module of `model.losses`, `model.metrics`, and `model.visualizers` to the head that the `attached_to` field of
   the module names. The module goes into the `losses`, `metrics`, or `visualizers` list of the head, without `attached_to`.
 * It sets `version` to the installed version.

A node is a head when its `name` contains `"Head"`. The function finds a head by its `alias`, or by its `name` when the head has
no alias.

The function logs a message at the `INFO` level when it finds `config_version`. It also logs one when the config is already
current, and one when the upgrade starts. At the same level, it logs each renamed field, each moved `params.variant`, each moved
module, and the new `version`. The removal of `tuner`, the change of `params.download_weights`, and the move of
`exporter.output_names` have no `INFO` message.

Parameters

 * `config` (`PathType | Params`): The path of a local YAML or JSON config file, or the config as a dictionary.

Returns

 * `Params`: The migrated config.

Raises

 * `ValueError`: When a module in `model.losses`, `model.metrics`, or `model.visualizers` has no `attached_to` field, or when
   `attached_to` does not name a head.

### upgrade_installation

```python
def upgrade_installation():
```

Upgrade the installed `luxonis-train` package from PyPI.

If a newer release exists, upgrade `pip`, `luxonis_train`, and `luxonis_ml[data]` with the current Python interpreter. A failed
version check is logged and leaves the installation unchanged.
