Ranges
Organizing telemetry data with time-based ranges and metadata.
This guide covers working with Ranges - time-based categorizations of data with metadata. Ranges are useful for organizing telemetry into tests, runs, events, or other logical groupings.
Range Configuration Reference
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | - | Human-readable name for the range |
time_range | TimeRange | Yes | - | Time interval spanned by the range (start must be ≤ end) |
color | string | No | "" | Hex color code for identifying the range in visualizations (e.g., "#FF0000") |
key | UUID | No | Auto | Unique identifier (automatically generated by Synnax if not provided) |
parent | ID | No | None | Optional parent ontology item for creating child ranges |
retrieve_if_name_exists | boolean | No | False | If True, retrieves existing range with same name and time range instead of creating a duplicate. Python only. |
TypeScript does not support conditional range creation.
Understanding a Time Range
A TimeRange defines the start and end boundaries of a range. It specifies the time
interval that the range covers.
Key Points:
- Start and End: A
TimeRangehas two properties:startandend, both of which are timestamps - End-Exclusive: The range includes data from
startup to (but not including)end - Validation: The
starttime must be less than or equal to theendtime
import synnax as sy
# Create a TimeRange using TimeStamp and TimeSpan
start = sy.TimeStamp.now()
end = start + sy.TimeSpan.HOUR * 2
time_range = sy.TimeRange(start=start, end=end)
# Using the convenience method span_range (recommended)
start = sy.TimeStamp.now()
time_range = start.span_range(sy.TimeSpan.HOUR * 2)
# Create from specific date/time (use string parsing)
start = sy.TimeStamp("2023-02-12 12:30:00")
end = sy.TimeStamp("2023-02-12 14:30:00")
time_range = sy.TimeRange(start=start, end=end) import { TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/client";
// Create a TimeRange using TimeStamp and TimeSpan
const start = TimeStamp.now();
const end = start.add(TimeSpan.hours(2));
const timeRange = new TimeRange(start, end);
// Using the convenience span method (recommended)
const start = TimeStamp.now();
const timeRange = start.span(TimeSpan.hours(2));
// Create from specific date/time
const start = new TimeStamp("2023-02-12 12:30:00");
const end = new TimeStamp("2023-02-12 14:30:00");
const timeRange = new TimeRange(start, end); Creating Ranges
To create a range, use the client.ranges.create method:
import synnax as sy
# Create a range with a specific time interval
start = sy.TimeStamp("2023-02-12 12:30:00")
end = sy.TimeStamp("2023-02-12 14:30:00")
my_range = client.ranges.create(
name="My Range",
time_range=sy.TimeRange(start=start, end=end),
)
# Or use the convenience span_range method
start = sy.TimeStamp.now()
my_range = client.ranges.create(
name="My Range",
time_range=start.span_range(sy.TimeSpan.HOUR * 2),
) import { TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/client";
// Create a range with a specific time interval
const start = new TimeStamp("2023-02-12 12:30:00");
const end = new TimeStamp("2023-02-12 14:30:00");
const range = await client.ranges.create({
name: "My Range",
timeRange: new TimeRange(start, end),
});
// Or use the convenience span method
const start = TimeStamp.now();
const range = await client.ranges.create({
name: "My Range",
timeRange: start.span(TimeSpan.hours(2)),
}); Synnax will automatically generate a unique identifier for the range.
Only Create a Range if it Does Not Exist
To create a range only if one with the same name doesn’t already exist, pass in the
retrieve_if_name_exists parameter:
import synnax as sy
client = sy.Client()
start = sy.TimeStamp("2023-02-12 12:30:00")
end = sy.TimeStamp("2023-02-12 14:30:00")
my_range = client.ranges.create(
name="My Range",
time_range=sy.TimeRange(start=start, end=end),
retrieve_if_name_exists=True,
)
# In the event the range already exists, Synnax will return
# the existing range instead of creating a new one.
TypeScript doesn’t have a built-in retrieveIfNameExists parameter, but you can
implement equivalent logic:
import { NotFoundError, TimeRange, TimeStamp } from "@synnaxlabs/client";
let myRange;
try {
myRange = await client.ranges.retrieve("My Range");
} catch (e) {
if (!(e instanceof NotFoundError)) throw e;
myRange = await client.ranges.create({
name: "My Range",
timeRange: new TimeRange(
new TimeStamp("2023-02-12 12:30:00"),
new TimeStamp("2023-02-12 14:30:00"),
),
});
} Creating Child Ranges
For more information on child ranges, see the ranges page.
child_range = parent_range.create_child_range(
name="My Child Range",
time_range=sy.TimeRange(
start=start,
end=end,
)
) const childRange = await client.ranges.create(
{
name: "My Child Range",
timeRange: new TimeRange(start, end),
},
{ parent: parentRange.ontologyID },
); Retrieving Ranges
Fetch a range using the client.ranges.retrieve method.
Retrieving a Single Range
Retrieve a range by its name or key. Synnax will raise a NotFoundError if the range
does not exist, and a MultipleFoundError if multiple ranges with the given name exist.
# By name
my_range = client.ranges.retrieve(name="My Range")
# By key
my_range = client.ranges.retrieve(key=my_range.key) // By name
const myRange = await client.ranges.retrieve("My Range");
// By key
const myRange = await client.ranges.retrieve(myRange.key); Retrieving Multiple Ranges
Retrieve multiple ranges by passing a list of names or keys. When retrieving multiple ranges, Synnax will not raise an error if a range cannot be found. Instead, the missing range will be omitted from the returned list.
# By name
my_ranges = client.ranges.retrieve(names=["My Range", "My Other Range"])
# By key
my_ranges = client.ranges.retrieve(keys=[my_range.key, my_other_range.key])
# By search term
ranges = client.ranges.search("Hotfire") // By name
const myRanges = await client.ranges.retrieve(["My Range", "My Other Range"]);
// By key
const myRanges = await client.ranges.retrieve([myRange.key, myOtherRange.key]);
// By search term
const ranges = await client.ranges.retrieve({ searchTerm: "Hotfire" }); Retrieving Child Ranges
If a range has child ranges, you can retrieve them directly from the parent range.
child_ranges = my_range.children const childRanges = await myRange.retrieveChildren(); Retrieving a Parent Range
Navigate up the hierarchy by retrieving a child range’s parent.
Retrieving a parent range is not directly supported in Python.
const parentRange = await myRange.retrieveParent(); Updating a Range
To update an existing range, use the same client.ranges.create method but specify the
key of the range to update. This allows modification of the range’s name, time range,
or color.
import synnax as sy
# First, retrieve the range you want to update
my_range = client.ranges.retrieve(name="My Range")
# Update the range by providing its key and new values
updated_range = client.ranges.create(
key=my_range.key, # Specify the key to update existing range
name="My Updated Range", # New name
time_range=sy.TimeRange(
start=sy.TimeStamp("2023-02-12 13:00:00"),
end=sy.TimeStamp("2023-02-12 15:00:00"),
),
color="#00FF00", # New color
) import { TimeRange, TimeStamp } from "@synnaxlabs/client";
// First, retrieve the range you want to update
const myRange = await client.ranges.retrieve("My Range");
// Update the range by providing its key and new values
const updatedRange = await client.ranges.create({
key: myRange.key, // Specify the key to update existing range
name: "My Updated Range", // New name
timeRange: new TimeRange(
new TimeStamp("2023-02-12 13:00:00"),
new TimeStamp("2023-02-12 15:00:00"),
),
color: "#00FF00", // New color
}); When updating a range, you must provide the key parameter. If you provide a key that
doesn’t exist, Synnax will create a new range with that key instead of raising an error.
Updating a range will completely replace its properties. Make sure to include all the properties you want to keep, not just the ones you want to change.
Attaching Metadata
Ranges can store metadata as key-value pairs. This is useful for attaching information like test configuration parameters, numeric results, or part numbers.
All metadata values are stored as strings. It’s up to you to correctly cast the values to the appropriate type.
Setting Metadata
my_range = client.ranges.retrieve(name="My Range")
# Set a single key/value pair
my_range.meta_data.set("part_number", "12345")
# Another way to set a single key/value pair
my_range.meta_data["part_number"] = "12345"
# Set multiple key/value pairs
my_range.meta_data.set({
"part_number": "12345",
"test_configuration": "Test 1",
"test_result": "123.45",
}) const myRange = await client.ranges.retrieve("My Range");
// Set a single key/value pair
await myRange.kv.set("part_number", "12345");
// TypeScript only supports the set() method
// (no bracket syntax)
// Set multiple key/value pairs
await myRange.kv.set({
part_number: "12345",
test_configuration: "Test 1",
test_result: "123.45",
}); Getting Metadata
my_range = client.ranges.retrieve(name="My Range")
# Retrieve a single key
part_number = my_range.meta_data["part_number"] # Or use .get("key")
# Retrieve multiple keys
metadata = my_range.meta_data.get(["part_number", "test_configuration"])
# List all metadata
all_metadata = my_range.meta_data.get([]) const myRange = await client.ranges.retrieve("My Range");
// Retrieve a single key
const partNumber = await myRange.kv.get("part_number");
// Retrieve multiple keys
const metadata = await myRange.kv.get(["part_number", "test_configuration"]);
// List all metadata
const allMetadata = await myRange.kv.list(); Deleting Metadata
my_range = client.ranges.retrieve(name="My Range")
# Delete a single key
del my_range.meta_data["part_number"] # Or use .delete("key")
# Delete multiple keys
my_range.meta_data.delete(["part_number", "test_configuration"]) const myRange = await client.ranges.retrieve("My Range");
// Delete a single key
await myRange.kv.delete("part_number");
// Delete multiple keys
await myRange.kv.delete(["part_number", "test_configuration"]); Reading from Ranges
Ranges provide a convenient way to read data without specifying exact time boundaries. Once you have a range, you can read channel data directly from it.
my_range = client.ranges.retrieve("My Interesting Test")
# Read data from a single channel
data = my_range.my_precise_tc.read()
# Read data from multiple channels
frame = my_range.read(["my_precise_tc", "my_precise_pt"])
# Python allows direct use of channel names as properties
celsius = my_range.my_precise_tc - 273.15 const myRange = await client.ranges.retrieve("My Interesting Test");
// Read data from a single channel
const data = await myRange.read("my_precise_tc");
// Read data from multiple channels
const frame = await myRange.read(["my_precise_tc", "my_precise_pt"]);
// Convert from Kelvin to Celsius
const celsius = data.map((v) => v - 273.15); Writing to Ranges
Writing to a range removes the burden of needing to correctly align the timestamps for different channels. The write will assume that the timestamp of the first sample is the start of the range.
my_range = client.ranges.retrieve("My Interesting Test")
temperatures = [55, 55.1, 55.7, 57.2, 58.1, 58.9, 59.1, 59.2, 59.3]
pressures = [100, 100.1, 100.7, 102.2, 103.1, 103.9, 104.1, 104.2, 104.3]
my_range.write({
"my_precise_tc": temperatures,
"my_precise_pt": pressures,
}) Writing to ranges is not supported in TypeScript.
Working with Channels
Ranges allow you to define aliases for channels that only apply within that range’s context. This is useful when you want to give a channel a more descriptive name for a specific test or operation.
Channel Aliases
Channels must maintain their original names, but you may want to refer to a channel
differently in the context of a particular range. For example, you might want to call
daq_analog_input_1 as tank_pressure for a burst test.
burst_test = client.ranges.retrieve(name="Oct 10 Burst Test")
# Set an alias for a channel
burst_test.set_alias("daq_analog_input_1", "tank_pressure")
# Access the channel using its alias
burst_test.tank_pressure
# List all aliases on the range
aliases = burst_test.list_aliases()
# Delete an alias
burst_test.delete_alias(channel_key) const burstTest = await client.ranges.retrieve("Oct 10 Burst Test");
// Set an alias for a channel
await burstTest.setAlias("daq_analog_input_1", "tank_pressure");
// Resolve an alias back to its channel key
const channelKey = await burstTest.resolveAlias("tank_pressure");
// List all aliases on the range
const aliases = await burstTest.listAliases();
// Delete an alias
await burstTest.deleteAlias(channelKey); Aliases are only valid within the context of a particular range. If you try to access an aliased channel outside of the range, Synnax will not be able to find it.
Accessing Aliased Channels
Once you’ve set an alias, you can access the channel using that alias.
Python allows you to access aliased channels directly as properties:
burst_test = client.ranges.retrieve(name="Oct 10 Burst Test")
# Access by alias as a property
data = burst_test.tank_pressure
# Or use dictionary syntax
data = burst_test["tank_pressure"]
# Regex returns a list of matching channels
tank_channels = burst_test["^tank"] TypeScript requires resolving the alias to a channel key first:
const burstTest = await client.ranges.retrieve("Oct 10 Burst Test");
// Resolve alias to channel key, then read
const channelKey = await burstTest.resolveAlias("tank_pressure");
// Then read by channel key
const data = await burstTest.read(channelKey);
// Or read directly by original channel name
const data2 = await burstTest.read("daq_analog_input_1"); Deleting Ranges
Delete a range by passing its key to the client.ranges.delete method.
# Delete a single range
client.ranges.delete(my_range.key)
# Delete multiple ranges
client.ranges.delete([my_range.key, my_other_range.key]) // Delete a single range
await client.ranges.delete(myRange.key);
// Delete multiple ranges
await client.ranges.delete([myRange.key, myOtherRange.key]);