Ranges
Categorize your data with ranges.
Ranges identify specific periods of time with a name. We can use the Python client to read from, write to, and attach metadata to these ranges.
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) |
retrieve_if_name_exists | boolean | No | False | If True, retrieves existing range with same name and time range instead of creating a duplicate |
parent | ID | No | None | Optional parent ontology item for creating child ranges |
Understanding time_range
time_rangeA 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
Example:
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) Creating Ranges
To create a range, we can 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(
# This name does not need to be unique, but it's a good idea to
# pick something that will be easy to identify later.
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),
) Synnax will automatically generate a unique identifier for the range.
Only Create a Range if it Doesn’t Exist
If we only want to create a range if one with the same name doesn’t already exist, we
can pass in the retrieve_if_name_exists parameter:
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.
Creating Child Ranges
For more information on child ranges, see the ranges page.
start = sy.TimeStamp("2023-02-12 12:30:00")
end = sy.TimeStamp("2023-02-12 14:30:00")
child_range = parent_range.create_child_range(
name="My Child Range",
time_range=sy.TimeRange(start=start, end=end),
) Retrieving Ranges
We can fetch a range using the client.ranges.retrieve method.
Retrieving a Single Range
We can retrieve a range by its name or key:
# By name
my_range = client.ranges.retrieve(name="My Range")
# By key
my_range = client.ranges.retrieve(key=my_range.key) Synnax will raise a NotFoundError if the range does not exist, and a
MultipleFoundError if multiple ranges with the given name exist. If you’d like to
accept multiple or no results, provide a list to the retrieve method as shown below.
The retrieve method only supports passing in named parameters i.e. name, key,
names, keys. If you try to pass in unnamed parameters, Synnax will raise a
TypeError.
Retrieving Multiple Ranges
We can retrieve multiple ranges by passing a list of names or keys to the retrieve
method:
# 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])
# This won't work!
my_ranges = client.ranges.retrieve(names=["My Range", my_other_range.key]) In these examples, Synnax will not raise an error if a range cannot be found. Instead, the missing range will be omitted from the returned list.
Retrieving Child Ranges
We can retrieve child ranges using the children property.
child_ranges = my_range.children Updating a Range
To update an existing range, we use the same client.ranges.create method but specify
the key of the range we want to update. This allows us to modify 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
) 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.
Working with Channels
Accessing Channels
We can access the channels on a range as if they were class properties or dictionary keys:
my_range = client.ranges.retrieve(name="My Range")
# Using a property accessor
my_pressure_channel = my_range.pressure_2
# Using a dictionary accessor
my_pressure_channel = my_range["pressure_2"] Accessing Multiple Channels
We can also access multiple channels on the range by passing a regular expression to our property accessor:
my_range = client.ranges.retrieve(name="My Range")
# Returns an iterable object containing matching channels
my_pressure_channels = my_range["^pressure"] If we try to access channel-specific methods on the returned object, such as a name or
data, Synnax will raise MultipleFoundError. Instead, we should iterate over the
returned list. Here’s a simple example where we plot the data from all of our pressure
channels:
import matplotlib.pyplot as plt
for ch in my_range["^pressure"]:
plt.plot(my_range.timestamps, ch, label=ch.name) This iteration pattern is valid even if we only have one channel that matches our regular expression.
Aliasing Channels
Channels must maintain their original names, but situations arise where we’d like to give a channel a more descriptive name in the context of a particular range. Ranges allow us to do just that.
Imagine we have a channel named daq_analog_input_1 that we’d like to refer to as
tank_pressure for a tank burst test. We can do this by aliasing the channel:
burst_test = client.ranges.retrieve(name="Oct 10 Burst Test")
# Set our alias
burst_test.daq_analog_input_1.set_alias("tank_pressure")
# We can also set an alias like this
burst_test.set_alias("daq_analog_input_1", "tank_pressure") We can now access the channel using its alias:
burst_test.tank_pressure Subsequent calls to set_alias will overwrite the previous alias.
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.
Attaching Metadata
Setting Metadata
It’s common to have non-data information we’d like to attach to a particular range, such
as test configuration parameters, numeric results, part numbers, etc. We can attach this
metadata to a range using the meta_data property:
burst_test = client.ranges.retrieve(name="Oct 10 Burst Test")
# Set a single key/value pair
burst_test.meta_data.set("part_number", "12345")
# Another way to set a single key/value pair
burst_test.meta_data["part_number"] = "12345"
# Set multiple key/value pairs
burst_test.meta_data.set({
"part_number": "12345",
"test_configuration": "Test 1",
"test_result": "123.45",
}) All metadata values are stored as strings. It’s up to you to correctly cast the values to the appropriate type.
Getting Metadata
Getting metadata is as easy as setting it:
burst_test = client.ranges.retrieve(name="Oct 10 Burst Test")
# Retrieve a single key
part_number = burst_test.meta_data.get("part_number")
# Another way to retrieve a single key
part_number = burst_test.meta_data["part_number"] Deleting Metadata
We can delete metadata using the delete method:
burst_test = client.ranges.retrieve(name="Oct 10 Burst Test")
# Delete a single key
burst_test.meta_data.delete("part_number")
# Another way to delete a single key
del burst_test.meta_data["part_number"]
# Delete multiple keys
burst_test.meta_data.delete(["part_number", "test_configuration"]) Deleting Ranges
Deleting a range is as simple as passing in its name or key to the delete method:
client.ranges.delete(name="My Range")
client.ranges.delete(key=my_range.key) Deleting a range by name will delete all ranges with that name. Be careful!
We can delete multiple ranges by passing a list of names or keys to the delete method:
client.ranges.delete(names=["My Range", "My Other Range"])
client.ranges.delete(keys=[my_range.key, my_other_range.key])