Skip to content

Commit

Permalink
Migration poetry (#10)
Browse files Browse the repository at this point in the history
* Bump version

* Refactoring (httpx, new endpoints, code removal, etc.)

* Add basic unittests

* Migrate to pyproject

* Update example

* Move tests

* Update worksflow
  • Loading branch information
fabaff authored Oct 15, 2021
1 parent b8b07cd commit d1bed07
Show file tree
Hide file tree
Showing 12 changed files with 874 additions and 124 deletions.
35 changes: 28 additions & 7 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Python package and lint
name: Testing

on:
push:
Expand All @@ -15,15 +15,36 @@ jobs:
python-version: [ 3.8, 3.9 ]

steps:
- uses: actions/checkout@v2
- name: Checkout
uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
installer-parallel: true

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v2
with:
path: .venv
key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}

- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root

- name: Install library
run: poetry install --no-interaction

- name: Run tests
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Black Code Formatter
uses: lgeiger/[email protected]
source .venv/bin/activate
pytest tests/
1 change: 0 additions & 1 deletion MANIFEST.in

This file was deleted.

34 changes: 21 additions & 13 deletions example.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
"""Sample code to interact with a Netdata instance."""
import asyncio
import aiohttp
import json

from netdata import Netdata


async def main():
"""Get the data from a Netdata instance."""
async with aiohttp.ClientSession() as session:
data = Netdata("localhost", loop, session)
# Get data for the CPU
await data.get_data("system.cpu")
print(json.dumps(data.values, indent=4, sort_keys=True))

# Print the current value of the system's CPU
print("CPU System:", round(data.values["system"], 2))

# Get the alarms which are present
await data.get_alarms()
print(data.alarms)
client = Netdata("localhost")

# Get all metrics
await client.get_info()
print(client.info)

# Get details of a available chart
await client.get_chart("system.cpu")
print(json.dumps(client.values, indent=4, sort_keys=True))

# Print the current value of the system's CPU
print("CPU System:", round(client.values["system"], 2))

# Get the alarms which are present
await client.get_alarms()
print(client.alarms)

# Get all metrics
await client.get_allmetrics()
print(client.metrics)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
104 changes: 48 additions & 56 deletions netdata/__init__.py
Original file line number Diff line number Diff line change
@@ -1,90 +1,82 @@
"""Client to retrieve data from a Netdata instance."""
import asyncio
import logging
import socket
from typing import Dict

import aiohttp
import async_timeout
import httpx
from yarl import URL

from . import exceptions

_LOGGER = logging.getLogger(__name__)
_DATA_ENDPOINT = "data?chart={resource}&before=0&after=-1&options=seconds"
_ALARMS_ENDPOINT = "alarms?all&format=json"
_ALL_METRIC_ENDPOINT = (
DATA_ENDPOINT = "data?chart={resource}&before=0&after=-1&options=seconds"
ALARMS_ENDPOINT = "alarms?all&format=json"
ALL_METRIC_ENDPOINT = (
"allmetrics?format=json&help=no&types=no&" "timestamps=yes&names=yes&data=average"
)
ALARM_COUNT = "alarm_count?context={resource}&status=RAISED"

API_VERSION = 1


class Netdata(object):
"""A class for handling connections with a Netdata instance."""

def __init__(self, host, loop, session, port=19999, path=None):
def __init__(self, host, port=19999, tls=None, path=None):
"""Initialize the connection to the Netdata instance."""
self._loop = loop
self._session = session
self.host = host
self.port = port
self.values = self.alarms = self.metrics = None
if path is None:
self.base_url = URL.build(scheme="http", host=host, port=port, path=f"/api/v{API_VERSION}/")
else:
self.base_url = URL.build(scheme="http", host=host, port=port, path=path)

self.scheme = "http" if tls is None or not False else "https"

async def get_data(self, resource):
"""Get detail for a resource from the data endpoint."""
self.endpoint = _DATA_ENDPOINT
url = "{}{}".format(self.base_url, self.endpoint.format(resource=resource))
if path is None:
self.base_url = URL.build(
scheme=self.scheme, host=host, port=port, path=f"/api/v{API_VERSION}/"
)
else:
self.base_url = URL.build(
scheme=self.scheme, host=host, port=port, path=path
)

async def get_data(self, url) -> Dict:
"""Execute a request to a data endpoint."""
try:
with async_timeout.timeout(5, loop=self._loop):
response = await self._session.get(url)
async with httpx.AsyncClient() as client:
response = await client.get(str(url))
except httpx.ConnectError:
raise exceptions.NetdataConnectionError(
f"Connection to {self.scheme}://{self.host}:{self.port} failed"
)

if response.status_code == httpx.codes.OK:
_LOGGER.debug(response.json())
try:
return response.json()
except TypeError:
_LOGGER.error("Can not load data from Netdata")
raise exceptions.NetdataError("Unable to get the data from Netdata")

async def get_chart(self, resource):
"""Get the details about a chart."""
url = URL(self.base_url) / DATA_ENDPOINT.format(resource=resource)
data = await self.get_data(url)

_LOGGER.info("Response from Netdata: %s", response.status)
data = await response.json()
_LOGGER.debug(data)
try:
self.values = {k: v for k, v in zip(data["labels"], data["data"][0])}

except (asyncio.TimeoutError, aiohttp.ClientError, socket.gaierror):
_LOGGER.error("Can not load data from Netdata")
raise exceptions.NetdataConnectionError()
except TypeError:
raise exceptions.NetdataError("Format of data doesn't match")

async def get_alarms(self):
"""Get alarms for a Netdata instance."""
self.endpoint = _ALARMS_ENDPOINT
url = "{}{}".format(self.base_url, self.endpoint)

try:
with async_timeout.timeout(5, loop=self._loop):
response = await self._session.get(url)

_LOGGER.debug("Response from Netdata: %s", response.status)
data = await response.json()
_LOGGER.debug(data)
self.alarms = data

except (asyncio.TimeoutError, aiohttp.ClientError, socket.gaierror):
_LOGGER.error("Can not load data from Netdata")
raise exceptions.NetdataConnectionError()
url = URL(self.base_url) / ALARMS_ENDPOINT
self.alarms = await self.get_data(url)

async def get_allmetrics(self):
"""Get all available metrics from a Netdata instance."""
self.endpoint = _ALL_METRIC_ENDPOINT
url = "{}{}".format(self.base_url, self.endpoint)

try:
with async_timeout.timeout(5, loop=self._loop):
response = await self._session.get(url)

_LOGGER.debug("Response from Netdata: %s", response.status)
data = await response.json()
_LOGGER.debug(data)
self.metrics = data
url = URL(self.base_url) / ALL_METRIC_ENDPOINT
self.metrics = await self.get_data(url)

except (asyncio.TimeoutError, aiohttp.ClientError, socket.gaierror):
_LOGGER.error("Can not load data from Netdata")
raise exceptions.NetdataConnectionError()
async def get_info(self):
"""Get information about the Netdata instance."""
url = URL(self.base_url) / "info"
self.info = await self.get_data(url)
Loading

0 comments on commit d1bed07

Please sign in to comment.