Skip to content

Commit d1bed07

Browse files
authored
Migration poetry (#10)
* Bump version * Refactoring (httpx, new endpoints, code removal, etc.) * Add basic unittests * Migrate to pyproject * Update example * Move tests * Update worksflow
1 parent b8b07cd commit d1bed07

File tree

12 files changed

+874
-124
lines changed

12 files changed

+874
-124
lines changed

.github/workflows/python-package.yml

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Python package and lint
1+
name: Testing
22

33
on:
44
push:
@@ -15,15 +15,36 @@ jobs:
1515
python-version: [ 3.8, 3.9 ]
1616

1717
steps:
18-
- uses: actions/checkout@v2
18+
- name: Checkout
19+
uses: actions/checkout@v2
20+
1921
- name: Set up Python ${{ matrix.python-version }}
2022
uses: actions/setup-python@v2
2123
with:
2224
python-version: ${{ matrix.python-version }}
25+
26+
- name: Install Poetry
27+
uses: snok/install-poetry@v1
28+
with:
29+
virtualenvs-create: true
30+
virtualenvs-in-project: true
31+
installer-parallel: true
32+
33+
- name: Load cached venv
34+
id: cached-poetry-dependencies
35+
uses: actions/cache@v2
36+
with:
37+
path: .venv
38+
key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
39+
2340
- name: Install dependencies
41+
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
42+
run: poetry install --no-interaction --no-root
43+
44+
- name: Install library
45+
run: poetry install --no-interaction
46+
47+
- name: Run tests
2448
run: |
25-
python -m pip install --upgrade pip
26-
pip install flake8 pytest
27-
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
28-
- name: Black Code Formatter
29-
uses: lgeiger/[email protected]
49+
source .venv/bin/activate
50+
pytest tests/

MANIFEST.in

Lines changed: 0 additions & 1 deletion
This file was deleted.

example.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
"""Sample code to interact with a Netdata instance."""
22
import asyncio
3-
import aiohttp
43
import json
54

65
from netdata import Netdata
76

87

98
async def main():
109
"""Get the data from a Netdata instance."""
11-
async with aiohttp.ClientSession() as session:
12-
data = Netdata("localhost", loop, session)
13-
# Get data for the CPU
14-
await data.get_data("system.cpu")
15-
print(json.dumps(data.values, indent=4, sort_keys=True))
16-
17-
# Print the current value of the system's CPU
18-
print("CPU System:", round(data.values["system"], 2))
19-
20-
# Get the alarms which are present
21-
await data.get_alarms()
22-
print(data.alarms)
10+
client = Netdata("localhost")
11+
12+
# Get all metrics
13+
await client.get_info()
14+
print(client.info)
15+
16+
# Get details of a available chart
17+
await client.get_chart("system.cpu")
18+
print(json.dumps(client.values, indent=4, sort_keys=True))
19+
20+
# Print the current value of the system's CPU
21+
print("CPU System:", round(client.values["system"], 2))
22+
23+
# Get the alarms which are present
24+
await client.get_alarms()
25+
print(client.alarms)
26+
27+
# Get all metrics
28+
await client.get_allmetrics()
29+
print(client.metrics)
30+
2331

2432
loop = asyncio.get_event_loop()
2533
loop.run_until_complete(main())

netdata/__init__.py

Lines changed: 48 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,82 @@
11
"""Client to retrieve data from a Netdata instance."""
2-
import asyncio
32
import logging
4-
import socket
3+
from typing import Dict
54

6-
import aiohttp
7-
import async_timeout
5+
import httpx
86
from yarl import URL
97

108
from . import exceptions
119

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

1918
API_VERSION = 1
2019

2120

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

25-
def __init__(self, host, loop, session, port=19999, path=None):
24+
def __init__(self, host, port=19999, tls=None, path=None):
2625
"""Initialize the connection to the Netdata instance."""
27-
self._loop = loop
28-
self._session = session
2926
self.host = host
3027
self.port = port
3128
self.values = self.alarms = self.metrics = None
32-
if path is None:
33-
self.base_url = URL.build(scheme="http", host=host, port=port, path=f"/api/v{API_VERSION}/")
34-
else:
35-
self.base_url = URL.build(scheme="http", host=host, port=port, path=path)
3629

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

38-
async def get_data(self, resource):
39-
"""Get detail for a resource from the data endpoint."""
40-
self.endpoint = _DATA_ENDPOINT
41-
url = "{}{}".format(self.base_url, self.endpoint.format(resource=resource))
32+
if path is None:
33+
self.base_url = URL.build(
34+
scheme=self.scheme, host=host, port=port, path=f"/api/v{API_VERSION}/"
35+
)
36+
else:
37+
self.base_url = URL.build(
38+
scheme=self.scheme, host=host, port=port, path=path
39+
)
4240

41+
async def get_data(self, url) -> Dict:
42+
"""Execute a request to a data endpoint."""
4343
try:
44-
with async_timeout.timeout(5, loop=self._loop):
45-
response = await self._session.get(url)
44+
async with httpx.AsyncClient() as client:
45+
response = await client.get(str(url))
46+
except httpx.ConnectError:
47+
raise exceptions.NetdataConnectionError(
48+
f"Connection to {self.scheme}://{self.host}:{self.port} failed"
49+
)
50+
51+
if response.status_code == httpx.codes.OK:
52+
_LOGGER.debug(response.json())
53+
try:
54+
return response.json()
55+
except TypeError:
56+
_LOGGER.error("Can not load data from Netdata")
57+
raise exceptions.NetdataError("Unable to get the data from Netdata")
58+
59+
async def get_chart(self, resource):
60+
"""Get the details about a chart."""
61+
url = URL(self.base_url) / DATA_ENDPOINT.format(resource=resource)
62+
data = await self.get_data(url)
4663

47-
_LOGGER.info("Response from Netdata: %s", response.status)
48-
data = await response.json()
49-
_LOGGER.debug(data)
64+
try:
5065
self.values = {k: v for k, v in zip(data["labels"], data["data"][0])}
51-
52-
except (asyncio.TimeoutError, aiohttp.ClientError, socket.gaierror):
53-
_LOGGER.error("Can not load data from Netdata")
54-
raise exceptions.NetdataConnectionError()
66+
except TypeError:
67+
raise exceptions.NetdataError("Format of data doesn't match")
5568

5669
async def get_alarms(self):
5770
"""Get alarms for a Netdata instance."""
58-
self.endpoint = _ALARMS_ENDPOINT
59-
url = "{}{}".format(self.base_url, self.endpoint)
60-
61-
try:
62-
with async_timeout.timeout(5, loop=self._loop):
63-
response = await self._session.get(url)
64-
65-
_LOGGER.debug("Response from Netdata: %s", response.status)
66-
data = await response.json()
67-
_LOGGER.debug(data)
68-
self.alarms = data
69-
70-
except (asyncio.TimeoutError, aiohttp.ClientError, socket.gaierror):
71-
_LOGGER.error("Can not load data from Netdata")
72-
raise exceptions.NetdataConnectionError()
71+
url = URL(self.base_url) / ALARMS_ENDPOINT
72+
self.alarms = await self.get_data(url)
7373

7474
async def get_allmetrics(self):
7575
"""Get all available metrics from a Netdata instance."""
76-
self.endpoint = _ALL_METRIC_ENDPOINT
77-
url = "{}{}".format(self.base_url, self.endpoint)
78-
79-
try:
80-
with async_timeout.timeout(5, loop=self._loop):
81-
response = await self._session.get(url)
82-
83-
_LOGGER.debug("Response from Netdata: %s", response.status)
84-
data = await response.json()
85-
_LOGGER.debug(data)
86-
self.metrics = data
76+
url = URL(self.base_url) / ALL_METRIC_ENDPOINT
77+
self.metrics = await self.get_data(url)
8778

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

0 commit comments

Comments
 (0)