Data Points -- Thermometer Readings
The Analogy
Every time you check a thermometer and write down the number, you're creating a data point. A data point is the smallest unit of information in a time-series database.
It has three parts:
- Timestamp -- When did you look at the thermometer? (e.g. "July 4, 2024 at 2:00 PM")
- Value -- What did it read? (e.g. 72.5°F)
- Tags -- Extra labels that help you find it later (e.g. sensor="kitchen", floor="1")
In Code
from datetime import datetime, timezone
from pytsdb import DataPoint
reading = DataPoint(
timestamp=datetime(2024, 7, 4, 14, 0, tzinfo=timezone.utc),
value=72.5,
tags={"sensor": "kitchen", "floor": "1"},
)
print(reading.value) # 72.5
print(reading.tags) # {'sensor': 'kitchen', 'floor': '1'}
Why Are Tags Useful?
Imagine you have 10 temperature sensors around your house. Without tags, you'd need 10 separate series. With tags, you can store everything in one series and filter:
# "Show me only the kitchen readings"
reading.matches_tags({"sensor": "kitchen"}) # True
reading.matches_tags({"sensor": "garage"}) # False
Fun Fact
Data points in pytsdb are frozen (immutable) -- once created, you can't change them. This is like writing in pen instead of pencil. It keeps your data trustworthy!
This even applies to the tags. When you create a data point, it takes its own private copy of the tags dictionary, so editing the dict you passed in later can't sneak in and change the point:
labels = {"sensor": "kitchen"}
reading = DataPoint(value=72.5, tags=labels)
labels["sensor"] = "garage" # edit the original dict
print(reading.tags) # {'sensor': 'kitchen'} -- still in pen!
A series works the same way in the other direction. Every point it hands back -- from query, first, last or looping over it -- is a fresh copy, so scribbling on a returned point's tags can't change what the database has stored.
Watch Out: Timestamps Need a Timezone
A data point's timestamp must be timezone-aware so pytsdb always knows which clock you mean. A bare datetime(2024, 1, 1) has no timezone, so pytsdb raises a friendly error instead of letting it cause confusing crashes later:
from datetime import datetime, timezone
DataPoint(timestamp=datetime(2024, 1, 1)) # ValueError!
DataPoint(timestamp=datetime(2024, 1, 1, tzinfo=timezone.utc)) # works
Watch Out: NaN Isn't a Reading
float("nan") means "not a number". It isn't bigger, smaller or equal to anything -- not even itself -- so a NaN reading would make "the highest reading" and "in time order" meaningless. pytsdb refuses it with a ValueError. So is anything that isn't a real number (a string, None, True). Infinity is fine.