Skip to content

DOCS-3642: Update optional dependencies #4309

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 9 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 79 additions & 2 deletions docs/dev/reference/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,83 @@ date: "2024-09-18"
# updated: "" # When the content was last entirely checked
---

{{% changelog color="changed" title="Deprecated explicit dependencies" date="2025-05-05" %}}

Some older modules use "explicit dependencies" which require users to list the names of dependencies in the `depends_on` field of the resource's configuration, for example:

```json {class="line-numbers linkable-line-numbers" data-line="8"}
{
"name": "mime-type-sensor",
"api": "rdk:component:sensor",
"model": "jessamy:my-module:my-sensor",
"attributes": {
"camera_name": "camera-1"
},
"depends_on": ["camera-1"]
}
```

This explicit dependency handling is deprecated and not recommended when writing new modules.

We now recommend using dependencies as described in the [Module dependencies](/operate/get-started/other-hardware/dependencies/) documentation.
The implicit way of handling dependencies does not require users to list the names of dependencies in the `depends_on` field.

{{% /changelog %}}

{{% changelog color="changed" title="Validate method signature for optional dependencies" date="2025-05-05" %}}

The `Validate` method signature in Python and Go modules has been updated to better distinguish between required and optional dependencies:

{{< tabs >}}
{{% tab name="Python" %}}

Old signature:

```python {class="line-numbers linkable-line-numbers"}
def validate_config(
cls, config: ComponentConfig
) -> Sequence[str]:
deps = []
return deps
```

New signature:

```python {class="line-numbers linkable-line-numbers"}
def validate_config(
cls, config: ComponentConfig
) -> Tuple[Sequence[str], Sequence[str]]:
req_deps = []
opt_deps = []
return req_deps, opt_deps
```

This is not currently a breaking change for Python modules, though the old signature is deprecated.

{{% /tab %}}
{{% tab name="Go" %}}

Old signature:

```go {class="line-numbers linkable-line-numbers"}
Validate(path string) ([]string, error)
```

New signature:

```go {class="line-numbers linkable-line-numbers"}
Validate(path string) (requiredDeps []string, optionalDeps []string, error)
```

This is a breaking change for Go modules.

{{% /tab %}}
{{< /tabs >}}

See [Module dependencies](/operate/get-started/other-hardware/dependencies/) for more information.

{{% /changelog %}}

<!-- If there is no concrete date for a change that makes sense, use the end of the month it was released in. -->

{{% changelog color="added" title="System log forwarding" date="2025-05-14" %}}
Expand Down Expand Up @@ -505,7 +582,7 @@ Viam has added a [sensor-controlled base](/operate/reference/components/base/sen

{{% changelog date="2024-01-31" color="added" title="Visualize captured data" %}}

You can now [visualize your data](/data-ai/data/visualize/) using many popular third-party visualization tools, including Grafana, Tableau, Googles Looker Studio, and more.
You can now [visualize your data](/data-ai/data/visualize/) using many popular third-party visualization tools, including Grafana, Tableau, Google's Looker Studio, and more.
You can visualize any data, such as sensor readings, that you have [synced](/data-ai/capture-data/capture-sync/) to the Viam app from your machine.

See [Visualize data with Grafana](/tutorials/services/visualize-data-grafana/) for a full walkthrough focused on Grafana specifically.
Expand Down Expand Up @@ -627,7 +704,7 @@ You now have the capability to use a [power sensor component](/operate/reference

{{% /changelog %}}

{{% changelog date="2023-09-30" color="added" title="Filter components data before the cloud" %}}
{{% changelog date="2023-09-30" color="added" title="Filter component's data before the cloud" %}}
Viam has written a module that allows you to filter data based on specific criteria before syncing it to [Viam's cloud](/data-ai/capture-data/capture-sync/).
It equips machines to:

Expand Down
95 changes: 44 additions & 51 deletions docs/operate/get-started/other-hardware/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ description: "Handle dependencies in your custom modular resource."
Dependencies are other {{< glossary_tooltip term_id="resource" text="resources" >}} that your modular resource needs to access in order to function.

For example, you could write a sensor component that requires a camera component, meaning that the camera is a dependency of that sensor.
The component configuration for the sensor could look like this, with the name of the camera as an attribute:

```json {class="line-numbers linkable-line-numbers" data-line="6"}
{
Expand All @@ -34,28 +35,34 @@ When [`viam-server` builds all the resources on a machine](/operate/get-started/
From within a module, you cannot access resources in the same way that you would in a client application.
For example, you cannot call `Camera.from_robot()` to get a camera resource.

Instead, you must access dependencies by writing your module code as follows:
Instead, you must access dependencies by writing your module code with dependencies:

### Required dependencies

Use required dependencies when your module should fail to build or reconfigure if a dependency does not successfully start.

`viam-server` will not build or reconfigure a module if the module has required dependencies that are not available.

{{< tabs >}}
{{% tab name="Python" %}}

1. In your modular resource's `validate_config` method, check the configuration attributes, then add the dependency name to the list of dependencies.
1. In your modular resource's `validate_config` method, check the configuration attributes, then add the dependency name to the first list of dependencies in the returned tuple:
For example:

```python {class="line-numbers linkable-line-numbers"}
@classmethod
def validate_config(cls, config: ComponentConfig) -> Sequence[str]:
deps = []
def validate_config(
cls, config: ComponentConfig
) -> Tuple[Sequence[str], Sequence[str]]:
req_deps = []
fields = config.attributes.fields
if not "camera_name" in fields:
if "camera_name" not in fields:
raise Exception("missing required camera_name attribute")
elif not fields["camera_name"].HasField("string_value"):
raise Exception("camera_name must be a string")
camera_name = fields["camera_name"].string_value
deps.append(camera_name)
return deps
req_deps.append(camera_name)
return req_deps, []
```

1. In your `reconfigure` method:
Expand Down Expand Up @@ -117,13 +124,13 @@ For full examples, see [<file>ackermann.py</file>](https://github.com/mcvella/vi
1. In your modular resource's `Validate` method, check the configuration attributes, then add the dependency name to the list of dependencies:

```go {class="line-numbers linkable-line-numbers"}
func (cfg *Config) Validate(path string) ([]string, error) {
var deps []string
func (cfg *Config) Validate(path string) (requiredDeps []string, optionalDeps []string, err error) {
var reqDeps []string
if cfg.CameraName == "" {
return nil, resource.NewConfigValidationFieldRequiredError(path, "camera_name")
return nil, nil, resource.NewConfigValidationFieldRequiredError(path, "camera_name")
}
deps = append(deps, cfg.CameraName)
return deps, nil
reqDeps = append(reqDeps, cfg.CameraName)
return reqDeps, nil, nil
}
```

Expand Down Expand Up @@ -172,46 +179,52 @@ If you need to maintain the state of your resource, see [(Optional) Create and e

### Optional dependencies

If an optional dependency does not start, the modular resource will continue to build and reconfigure without it.
`viam-server` reattempts to construct the optional dependency every 5 seconds.
When an optional dependency constructs successfully, your modular resource reconfigures so it can access the optional dependency.

Example use case for optional dependencies: If your module depends on multiple cameras, but can function even when some are unavailable, you can code the cameras as optional dependencies so that your module can construct and reconfigure without them.

{{< tabs >}}
{{% tab name="Python" %}}

If your module has optional dependencies, the steps are the same as for required dependencies, except that your `validate_config` method can treat the dependency as optional by returning an empty list if the dependency is not configured:
If your module has optional dependencies, the steps are the same as for required dependencies, except that your `validate_config` function should add the dependency to the second element of the returned tuple:

```python {class="line-numbers linkable-line-numbers"}
@classmethod
def validate_config(cls, config: ComponentConfig) -> Sequence[str]:
deps = []
def validate_config(
cls, config: ComponentConfig
) -> Tuple[Sequence[str], Sequence[str]]:
opt_deps = []
fields = config.attributes.fields
if "camera_name" not in fields:
self.logger.info(
"camera_name not configured, using no camera")
else:
if not fields["camera_name"].HasField("string_value"):
raise Exception("camera_name must be a string")
deps.append(fields["camera_name"].string_value)
return deps
raise Exception("missing required camera_name attribute")
elif not fields["camera_name"].HasField("string_value"):
raise Exception("camera_name must be a string")
camera_name = fields["camera_name"].string_value
opt_deps.append(camera_name)
return [], opt_deps
```

Be sure to handle the case where the dependency is not configured in your API implementation as well.
Be sure to handle the case where the dependency is not available in your API implementation as well.

{{% /tab %}}
{{% tab name="Go" %}}

If your module has optional dependencies, the steps are the same as for required dependencies, except that your `Validate` method can treat the dependency as optional by returning an empty list if the dependency is not configured:
If your module has optional dependencies, the steps are the same as for required dependencies, except that your `Validate` function should add the dependency to the second returned element:

```go {class="line-numbers linkable-line-numbers"}
func (cfg *Config) Validate(path string) ([]string, error) {
var deps []string
func (cfg *Config) Validate(path string) (requiredDeps []string, optionalDeps []string, err error) {
var optDeps []string
if cfg.CameraName == "" {
logger.Info("camera_name not configured, using no camera")
return nil, nil
return nil, nil, resource.NewConfigValidationFieldRequiredError(path, "camera_name")
}
deps = append(deps, cfg.CameraName)
return deps, nil
optDeps = append(optDeps, cfg.CameraName)
return nil, optDeps, nil
}
```

Be sure to handle the case where the dependency is not configured in your API implementation as well.
Be sure to handle the case where the dependency is not available in your API implementation as well.

{{% /tab %}}
{{< /tabs >}}
Expand All @@ -221,26 +234,6 @@ There is not currently an SDK method to directly access configuration attributes
You must access the API key module environment variables to establish the app client connection.
{{% /hiddencontent %}}

### Explicit dependencies (deprecated)

Some older modules use explicit dependencies, which require users to list the names of dependencies in the `depends_on` field of the resource's configuration, for example:

```json {class="line-numbers linkable-line-numbers"}
{
"name": "mime-type-sensor",
"api": "rdk:component:sensor",
"model": "jessamy:my-module:my-sensor",
"attributes": {
"camera_name": "camera-1"
},
"depends_on": ["camera-1"]
}
```

This is deprecated and not recommended when writing new modules.

Instead, we recommend using implicit dependencies (as shown in the examples above), which do not require users to list the names of dependencies in the `depends_on` field.

## Configure your module's dependencies more easily with a discovery service

If your module requires dependencies, you can make it easier for users to configure them by writing a [discovery service](/operate/reference/services/discovery/) as one model within your module.
Loading