> ## Documentation Index
> Fetch the complete documentation index at: https://docs.siftstack.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stream your first telemetry data with Python

> Send telemetry to Sift using Python.

After completing this topic, you can send telemetry from a Python script to Sift and see your data appear in a Run.

## Before you begin

* Python 3.x is installed.
* You have a Sift API key, gRPC URL, and REST URL. See [Authentication](/documentation/manage/set-up-api-access).

## How ingestion config streaming works

The Python client library uses ingestion-config-based streaming. When you define an `IngestionConfigFormPy`, Sift registers the schema of your telemetry (your Asset, Flows, and Channels) the first time you connect. On subsequent connections with the same `client_key`, Sift reuses that schema so you do not need to re-register it.

A Run groups the data you stream into a named, time-bounded capture. Runs are optional; you can stream data without one.

## Stream telemetry to Sift

1. Install the `sift-stack-py` package.

   ```bash theme={null}
   pip install sift-stack-py
   ```

2. In your `main.py`, import the required modules.

   ```python theme={null}
   import asyncio
   import random
   from datetime import datetime, timezone

   from sift_client import SiftClient, SiftConnectionConfig
   from sift_client.sift_types import RunCreate
   from sift_stream_bindings import (
       ChannelConfigPy,
       ChannelDataTypePy,
       ChannelValuePy,
       FlowConfigPy,
       FlowPy,
       IngestionConfigFormPy,
       TimeValuePy,
       ValuePy,
   )
   ```

3. Define a data source. The following example simulates sensor data by yielding a random float every 0.5 seconds for 60 seconds.

   ```python theme={null}
   async def data_source():
       start = asyncio.get_event_loop().time()
       while asyncio.get_event_loop().time() - start < 60:
           await asyncio.sleep(0.5)
           yield datetime.now(timezone.utc), random.uniform(0, 10)
   ```

4. Define the schema of your telemetry using `IngestionConfigFormPy` and `FlowConfigPy`.

   ```python theme={null}
   ASSET_NAME = "NostromoLV426"
   FLOW_NAME = "velocity-reading"
   CONFIG_KEY = "nostromo-lv-426-config-v1"

   flow_config = FlowConfigPy(
       name=FLOW_NAME,
       channels=[
           ChannelConfigPy(
               name="mainmotor.velocity",
               unit="m/s",
               data_type=ChannelDataTypePy.Double,
               description="Main motor velocity",
               enum_types=[],
               bit_field_elements=[],
           ),
       ],
   )

   ingestion_config = IngestionConfigFormPy(
       asset_name=ASSET_NAME,
       client_key=CONFIG_KEY,
       flows=[flow_config],
   )
   ```

   <Info>
     **Client keys**

     The `CONFIG_KEY` (client key) uniquely identifies your schema. Use the same key across restarts to reuse the registered schema. Adding new flows and channels over time is safe. Modifying existing flows or channels is not backwards compatible and will cause unexpected behavior.
   </Info>

5. Create a `SiftClient` and open a streaming session with a Run attached.

   ```python theme={null}
   async def main():
       connection_config = SiftConnectionConfig(
           api_key="your-api-key",
           grpc_url="your-grpc-url",
           rest_url="your-rest-url",
       )
       client = SiftClient(connection_config=connection_config)

       run = RunCreate(name=f"[{ASSET_NAME}].run", client_key=f"{CONFIG_KEY}.run")

       async with await client.async_.ingestion.create_ingestion_config_streaming_client(
           ingestion_config=ingestion_config,
           run=run,
       ) as ingest_client:
           async for timestamp, velocity in data_source():
               await ingest_client.send(
                   FlowPy(
                       flow_name=FLOW_NAME,
                       timestamp=TimeValuePy.from_timestamp_millis(int(timestamp.timestamp() * 1000)),
                       values=[
                           ChannelValuePy(name="mainmotor.velocity", value=ValuePy.Double(velocity)),
                       ],
                   )
               )
   ```

   <Info>
     **Runs are optional**

     Attaching a Run groups your data into a named capture. You can omit the `run` parameter if you want to stream data without grouping it into a Run.
   </Info>

6. Run the async entry point.

   ```python theme={null}
   if __name__ == "__main__":
       asyncio.run(main())
   ```

## Verify the result

After the script finishes, open Sift and navigate to the asset (in our example, `NostromoLV426`). You should see:

* The asset listed under your organization.
* The Run you created, with a start time matching when the script ran.
* Data for the channel(s) visible in Explore.

## Next steps

* [Organize streamed data into Assets and Runs](/documentation/ingest/stream/organize-streamed-data-into-assets-and-runs)
* [Stream telemetry from a running application](/documentation/ingest/stream/stream-telemetry-from-a-running-application)
* [Choose a streaming method](/documentation/ingest/stream/choose-a-streaming-method)

## Reference

* [Full Python code example](https://github.com/sift-stack/sift/tree/main/python/examples/ingestion-tutorial)
* [sift\_client documentation](https://sift-stack.github.io/sift/python/latest/reference/sift_client/)
* [sift-stack-py on PyPI](https://pypi.org/project/sift-stack-py/)
* [Ingestion config streaming reference](/documentation/reference/stream/ingestion-config-streaming-reference)
* [Client library reference](/documentation/reference/stream/client-library-reference)
