Skip to content

Commit

Permalink
Add initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
Bre77 committed Jan 7, 2024
1 parent 6f576f9 commit 2396b74
Show file tree
Hide file tree
Showing 7 changed files with 413 additions and 0 deletions.
85 changes: 85 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Publish Python 🐍 distribution 📦 to PyPI

on: push

jobs:
build:
name: Build distribution 📦
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Install pypa/build
run: >-
python3 -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: python3 -m build
- name: Store the distribution packages
uses: actions/upload-artifact@v3
with:
name: python-package-distributions
path: dist/

publish-to-pypi:
name: >-
Publish Python 🐍 distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
needs:
- build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/tesla_fleet_api
permissions:
id-token: write

steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: python-package-distributions
path: dist/
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

github-release:
name: >-
Upload to GitHub Release
needs:
- publish-to-pypi
runs-on: ubuntu-latest

permissions:
contents: write # IMPORTANT: mandatory for making GitHub Releases

steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: python-package-distributions
path: dist/
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
gh release create
'${{ github.ref_name }}'
--repo '${{ github.repository }}'
--notes ""
- name: Upload artifacts to GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
# Upload to GitHub Release using the `gh` CLI.
# `dist/` contains the built packages, and the
# sigstore-produced signatures and certificates.
run: >-
gh release upload
'${{ github.ref_name }}' dist/**
--repo '${{ github.repository }}'
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test.py
*.pyc
__pycache__
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Teslemetry Stream Library

```
async def main():
async with aiohttp.ClientSession() as session:
stream = TeslemetryStream(
token="<token>",
vin="<vin>",
session=session,
)
await stream.connect()
print("Connected")
async def listen(stream):
async for event in stream:
print(event)
asyncio.create_task(listen(stream))
await asyncio.sleep(20)
await stream.close()
```
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
aiohttp==3.*
24 changes: 24 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import setuptools

with open("README.md", "r") as fh:
long_description = fh.read()

setuptools.setup(
name="teslemetry_stream",
version="0.0.1",
author="Brett Adams",
author_email="[email protected]",
description="Teslemetry Streaming API library for Python",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/Teslemetry/teslemetry_stream",
packages=setuptools.find_packages(),
classifiers=[
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
],
python_requires=">=3.8",
install_requires=["aiohttp, asyncio"],
)
114 changes: 114 additions & 0 deletions teslemetry_stream/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import aiohttp
import asyncio
import json
from .const import TelemetryFields, TelemetryAlerts

SERVER = "http://192.168.1.3:4443"


class TeslemetryStream:
"""Teslemetry Stream Client"""

fields: dict[TelemetryFields, dict[str, int]]
alerts: list[TelemetryAlerts]
_update_lock: bool = False
_response: aiohttp.ClientResponse | None = None

def __init__(self, session: aiohttp.ClientSession, vin: str, token: str):
self._session = session
self.vin = vin
self._headers = {"Authorization": f"Bearer {token}"}

async def add_field(
self, field: TelemetryFields, interval: int, update: bool = True
) -> None:
"""Add field to telemetry stream."""
if not self.fields.get(field, {}).get("interval_seconds") == interval:
self.fields[field] = {"interval_seconds": interval}
if update:
await self.update()

async def remove_field(self, field: TelemetryFields, update: bool = True) -> None:
"""Remove field from telemetry stream."""
if field in self.fields:
del self.fields[field]
if update:
await self.update()

async def add_alert(self, alert: TelemetryAlerts, update: bool = True) -> None:
"""Add alert to telemetry stream."""
if alert not in self.alerts:
self.alerts.append(alert)
if update:
await self.update()

async def remove_alert(self, alert: TelemetryAlerts, update: bool = True) -> None:
"""Remove alert from telemetry stream."""
if alert in self.alerts:
self.alerts.remove(alert)
if update:
await self.update()

@property
def config(self) -> dict:
"""Return current configuration."""
return {"fields": self.fields, "alerts": self.alerts}

async def update(self, wait: int = 1) -> None:
"""Update the telemetry stream."""
if self._update_lock:
return
self._update_lock = True
await asyncio.sleep(wait)
await self._session.patch(
f"{SERVER}/{self.vin}",
headers=self._headers,
json=self.config,
)
self._update_lock = False

async def connect(self) -> None:
"""Connect to the telemetry stream."""
try:
self._response = await self._session.post(
f"{SERVER}/{self.vin}",
headers=self._headers,
raise_for_status=True,
timeout=aiohttp.ClientTimeout(connect=5, sock_read=30, total=None),
)
except aiohttp.ClientConnectionError as error:
raise error

async def close(self) -> None:
"""Close connection."""
if self._response is not None:
self._response.close()
self._response = None

async def __aenter__(self) -> "TeslemetryStream":
"""Connect and listen Server-Sent Event."""
await self.connect()
return self

async def __aexit__(self, *exc):
"""Close connection."""
await self.close()

def __aiter__(self):
"""Return"""
return self

async def __anext__(self) -> dict:
"""Return next event."""
if not self._response:
raise ValueError

try:
async for line_in_bytes in self._response.content:
line = line_in_bytes.decode("utf8")
print(line)
if line.startswith("data:"):
return json.loads(line[5:])
continue
except aiohttp.ClientConnectionError as error:
raise StopAsyncIteration from error
Loading

0 comments on commit 2396b74

Please sign in to comment.