Skip to content

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:

  1. Timestamp -- When did you look at the thermometer? (e.g. "July 4, 2024 at 2:00 PM")
  2. Value -- What did it read? (e.g. 72.5°F)
  3. 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.

DataPoint(value=float("nan"))  # ValueError!
DataPoint(value=float("inf"))  # works