Version range dependencies for Functions

In addition to depending on a pinned version of a Function, some applications like Workshop and Actions allow you to depend on a Function at a version range. Doing so enables automatic upgrades at runtime, which can save you time in your development cycle and provide a downtime-less upgrade experience for deployed Python Functions.

While version range dependencies are a powerful feature, they also carry certain risks (for example, there are permissioning consequences specific to Actions). This documentation explains the mechanics behind version range resolution so that you can better understand these risks and make an informed decision on whether version range dependencies are suitable for your application.

This documentation page assumes prior knowledge on topics like backward compatibility and the Semantic Versioning system. If you are not familiar with these topics, review our documentation on Functions versioning.

You should also be familiar with the rules around version precedence as defined in the Semantic Versioning specification ↗. In other words, you should be able to determine, given two distinct versions, which one has lower precedence. For example, 1.0.0-rc.1 < 1.0.0 < 1.0.1 < 1.1.0 < 2.0.0.

Version ranges

In its simplest form, a version range is a collection of version inequalities, and a version is said to "satisfy" a range if it satisfies all of its inequalities. For example, version 1.2.0 satisfies the range >=1.0.0 <2.0.0.

Internally, the semantics of Function version ranges are adopted from NPM, a popular package manager for the JavaScript ecosystem. Review the NPM documentation on version ranges ↗ for a rigorous definition.

Applications like Workshop and Actions currently only allow version ranges that comprise backward compatible versions (that is, minor or patch upgrades).

The NPM equivalent of this backward compatible range used by Workshop and Actions is the caret range ↗.

Version range resolution

With the exception of deployed Functions, when you depend on a Function at a version range, a concrete version that satisfies the range will be chosen at runtime during execution. In particular, the maximum satisfying version will be chosen on an eventual basis (it can take a few minutes to pick up new releases).

Deployed functions

For deployed Functions, a concrete version is instead resolved to the currently deployed version, if it satisfies the range. If the deployed version does not satisfy the range, an error will be returned.

Risks

While Functions developers are guided towards the Semantic Versioning specification and general best practices, it is always possible for breaks to be accidentally introduced in non-major version releases.

If your application picks up a breaking change, it can manifest in any number of problems, like runtime failures or unexpected behavior.

Upon noticing a breaking change, you should immediately contact the developer of the Function so that they can release a fix, and in the meantime, you should pin your Function dependency to the last working version.

With the caveat of deployed Function dependencies, if your application has strict uptime requirements and cannot tolerate any breaks, you should use pinned version dependencies.

Permissions and provenance in Actions

When using a version range in a Function rule of an Action type, users who do not have edit permissions on the Action type will be able to modify its behavior by making changes to the backing Function. This is because edit permissions on the Function are not necessarily tied to any permissions on the Action type.

The provenance of the Action type is set according to the provenance of the Function's minimum satisfying version in the chosen range. If, at runtime, an execution of the Function returns edits outside of this provenance (for example, a newer range-satisfying Function release returns edits for an additional Object type), the Action execution will fail.

Currently, the provenance consists only of the Object types that the Action type may edit at runtime.