# Quick Start

## Connecting to a server

Connecting to a server is easy with the supplied `connect` function from the `pingthings.timeseries` package.

```python
    >>> import pingthings as pt
    >>> # connect with a specific profile
    >>> conn = pt.connect(profile="my_cluster")
    >>> conn
    <pingthings.UnifiedClient at 0x...>
```

### Get Platform Information

```python
    >>> conn.info()
    {'major_version': ...,
     'minor_version': ...,
     'build': ...,
     'proxy': {'proxy_endpoints': [...]}}
```

Refer to [the connection API documentation page](api/unified_client.rst)


## Retrieving a Stream

In order to interact with data, you'll need to obtain or create a `Stream` object.
A number of options are available to get existing streams.

### Find streams by collection

Multiple streams are often organized under a single collection which is similar
to the concept of a directory path.  To search for all streams under a given
collection you can use the `streams_in_collection` method.

```python
    >>> streams = conn.streams_in_collection("USEAST_NOC1/90807")
    >>> for stream in streams:
    ...     print(stream.uuid, stream.name)
```

### Find stream by UUID

A method has also been provided if you already know the UUID of a single stream you
would like to retrieve. For convenience, this method accepts instances of either
`str` or `UUID`.

```python
    >>> stream = conn.stream_from_uuid("07d28a44-4991-492d-b9c5-2d8cec5aa6d4")
```


## Viewing a Stream's Data

To view data within a stream, you'll need to specify a time range to query for as
well as a version number (defaults to latest version).  Remember that the Predictive Grid Platform
stores data to the nanosecond (all timestamps are internally represented as UTC+0 time) and so Unix timestamps will need to be converted
if needed.

```python
    >>> import datetime as dt
    >>> start = pt.utils.datetime_to_ns(dt.datetime(2018,1,1,12,30))
    >>> end = start + pt.utils.ns_delta(hours=1)
    >>> data = stream.raw_values(start, end)
    >>> data
```

As shown above, [`pingthings.timeseries.utils`](pingthings.timeseries.utils) has
convenience functions to make it easier to deal with nanosecond
conversion and general handling of time deltas

```python
    >>> start = pt.utils.datetime_to_ns(dt.datetime(2018,1,1))
    >>> end = pt.utils.currently_as_ns()
    >>> data = stream.raw_values(start, end)
    >>> data
```

You can also view windows of data at arbitrary levels of detail.  One such
windowing feature is shown below.

```python
    >>> # find the aligned window length closest to 1 hour
    >>> point_width = pt.utils.nearest_point_width(hours=1)
    >>> print(point_width)
    datetime.timedelta(seconds=4398, microseconds=46511)
    >>> start = pt.utils.datetime_to_ns(dt.datetime(2018,1,1,12,30))
    >>> end = start + pt.utils.ns_delta(days=7)
    >>> stat_values = stream.windowed_values(start, end, point_width)
    >>> stat_values
```

## Using StreamSets
A `StreamSet` is a wrapper around a list of `Stream` objects with a
number of convenience methods available.  Future updates will allow you to
query for streams using a SQL-like syntax but for now you will need to provide
a list of UUIDs.

The StreamSet allows you to interact with a group of streams rather than at the
level of the individual `Stream` object.  Aside from being useful to see
concurrent data across streams, you can also easily transform the data to other
data structures or even serialize the data to disk in one operation.

Some quick examples are shown below but please review the [API docs](pingthings.timeseries.StreamSet) for the full
list of features.


```{note}
**Time ranges for queries**

In the following examples, notice that the end time is **not** inclusive of the data that is present at `end`.
*All** PingThings API data query operations follow the convention that captured data falls in the the range:
$[start, end)$
```


You're actually already familiar with `StreamSet`s, as that's the return type of the `streams_in_collection`
method showcased above. You can also create a `StreamSet` manually by providing a list of streams:

```python
    >>> streams = pt.timeseries.StreamSet([stream_1, stream_2, stream_3, ...])
    >>> for stream in streams:
    ...     print(stream.uuid, stream.name)
```

`StreamSet` objects have many of the same methods as `Stream`s. For example:

```python
    >>> streams.count()
    ...
    >>> start = pt.utils.datetime_to_ns(dt.datetime(2018,1,1,12,30))
    >>> end = start + pt.utils.ns_delta(hours=1)
    >>> data = streams.raw_values(start, end)
    >>> data
    ...
    >>> point_width = pt.utils.nearest_point_width(hours=1)
    >>> start = pt.utils.datetime_to_ns(dt.datetime(2018,1,1,12,30))
    >>> end = start + pt.utils.ns_delta(days=7)
    >>> stat_values = streams.windowed_values(start, end, point_width)
    >>> stat_values
```

Since `StreamSet` methods return `arrow` tables, it's straightforward to write
the data to disk:

```python
    >>> from pyarrow import csv
    >>> csv.write_csv(streams.raw_values(start, end), "stream_data.csv")
```

or convert it to a `pandas` DataFrame for further manipulation and plotting:

```python
    >>> df = streams.windowed_values(start, end, point_width).to_pandas()
    >>> df = df.set_index("time")
    >>> df = df.loc[:, [col for col in df if col.endswith("mean")]]
    >>> df = df.rename(
    ...     columns={
    ...         f"{stream.uuid}/mean": f"{stream.collection}.{stream.name}"
    ...         for stream in streams
    ...     }
    ... )
    >>> df.plot(title="Mean values", ylabel=streams[0].tags()["unit"])
```
