Skip to content
This repository has been archived by the owner on Apr 12, 2023. It is now read-only.

Commit

Permalink
Pass data as dataclass object (#1)
Browse files Browse the repository at this point in the history
* Use dataclass

* Use dataclass

* Next step

* Add translations

* Improve translations

* Improve translations

* Add py.typed file

* Add new trend strings

* Bump version

* Clean parsing logic

* Fix flake8 error

* Secure translate logic

* Bump version

* Add stale action

* Remove codeql action

* Use encoding parameter with open function

* Bump version
  • Loading branch information
bieniu authored Aug 27, 2021
1 parent 7080f5d commit a487f75
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 154 deletions.
37 changes: 0 additions & 37 deletions .github/workflows/codeql.yml

This file was deleted.

22 changes: 22 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Mark stale issues and pull requests

on:
schedule:
- cron: '25 8 * * *'

jobs:
stale:

runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write

steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Stale issue message'
stale-pr-message: 'Stale pull request message'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'
8 changes: 5 additions & 3 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
async def main():
async with ClientSession() as websession:
try:
zadnegoale = ZadnegoAle(websession, REGION)
data = await zadnegoale.async_update(alerts=True)
zadnegoale = ZadnegoAle(websession, REGION, debug=False)
dusts = await zadnegoale.async_get_dusts()
alerts = await zadnegoale.async_get_alerts()

except (ApiError, ClientError, InvalidRegionError) as error:
print(f"Error: {error}")
else:
print(f"Region: {zadnegoale.region_name}")
print(f"Data: {data}")
print(f"Dusts: {dusts}")
print(f"Alerts: {alerts}")


loop = asyncio.get_event_loop()
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
aiohttp
aiohttp
dacite
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name="zadnegoale",
version="0.3.0",
version="0.5.0",
author="Maciej Bieniek",
description="Python wrapper for getting allergen concentration data from Żadnego Ale servers.",
long_description=long_description,
Expand All @@ -16,6 +16,7 @@
url="https://github.com/bieniu/zadnegoale",
license="Apache-2.0 License",
packages=["zadnegoale"],
package_data={"nettigo_air_monitor": ["py.typed"]},
python_requires=">=3.6",
install_requires=list(val.strip() for val in open("requirements.txt")),
classifiers=[
Expand Down
90 changes: 21 additions & 69 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
@pytest.mark.asyncio
async def test_dusts_and_alerts():
"""Test with valid dusts and alerts data."""
with open("tests/fixtures/dusts.json") as file:
with open("tests/fixtures/dusts.json", encoding="utf-8") as file:
dusts = json.load(file)
with open("tests/fixtures/alerts.json") as file:
with open("tests/fixtures/alerts.json", encoding="utf-8") as file:
alerts = json.load(file)

session = aiohttp.ClientSession()
Expand All @@ -37,75 +37,27 @@ async def test_dusts_and_alerts():
)

zadnegoale = ZadnegoAle(session, VALID_REGION, debug=True)
result = await zadnegoale.async_update(alerts=True)
result_dusts = await zadnegoale.async_get_dusts()
result_alerts = await zadnegoale.async_get_alerts()

await session.close()

assert zadnegoale.region_name == "Karpaty"
assert len(result.sensors) == 8
assert result.sensors.cladosporium["value"] == 5
assert result.sensors.cladosporium["trend"] == "bez zmian"
assert result.sensors.cladosporium["level"] == "bardzo niskie"
assert result.sensors.cis["value"] == 1
assert result.sensors.cis["trend"] == "wzrost"
assert result.sensors.cis["level"] == "brak"
assert result.sensors.leszczyna["value"] == 5
assert result.sensors.leszczyna["trend"] == "bez zmian"
assert result.sensors.leszczyna["level"] == "bardzo niskie"
assert result.sensors.wiąz["value"] == 1
assert result.sensors.wiąz["trend"] == "bez zmian"
assert result.sensors.wiąz["level"] == "brak"
assert result.sensors.wierzba["value"] == 1
assert result.sensors.wierzba["trend"] == "bez zmian"
assert result.sensors.wierzba["level"] == "brak"
assert (
result.alerts["value"]
== "Wysokie stężenie pyłku olszy, bardzo niskie leszczyny."
)
try:
result.sensors.unknown
except AttributeError as error:
assert str(error) == "No such attribute: unknown"


@pytest.mark.asyncio
async def test_dusts():
"""Test with valid dusts data."""
with open("tests/fixtures/dusts.json") as file:
dusts = json.load(file)

session = aiohttp.ClientSession()

with aioresponses() as session_mock, patch(
"zadnegoale.date", today=Mock(return_value=TEST_DATE)
):
session_mock.get(
f"http://api.zadnegoale.pl/dusts/public/date/20210101/region/{VALID_REGION}",
payload=dusts,
)

zadnegoale = ZadnegoAle(session, VALID_REGION)
result = await zadnegoale.async_update()

await session.close()

assert zadnegoale.region_name == "Karpaty"
assert len(result.sensors) == 8
assert result.sensors.cladosporium["value"] == 5
assert result.sensors.cladosporium["trend"] == "bez zmian"
assert result.sensors.cladosporium["level"] == "bardzo niskie"
assert result.sensors.cis["value"] == 1
assert result.sensors.cis["trend"] == "wzrost"
assert result.sensors.cis["level"] == "brak"
assert result.sensors.leszczyna["value"] == 5
assert result.sensors.leszczyna["trend"] == "bez zmian"
assert result.sensors.leszczyna["level"] == "bardzo niskie"
assert result.sensors.wiąz["value"] == 1
assert result.sensors.wiąz["trend"] == "bez zmian"
assert result.sensors.wiąz["level"] == "brak"
assert result.sensors.wierzba["value"] == 1
assert result.sensors.wierzba["trend"] == "bez zmian"
assert result.sensors.wierzba["level"] == "brak"
assert result_dusts.cladosporium.value == 5
assert result_dusts.cladosporium.trend == "steady"
assert result_dusts.cladosporium.level == "very low"
assert result_dusts.yew.value == 1
assert result_dusts.yew.trend == "rising"
assert result_dusts.yew.level == "lack"
assert result_dusts.hazel.value == 5
assert result_dusts.hazel.trend == "steady"
assert result_dusts.hazel.level == "very low"
assert result_dusts.elm.value == 1
assert result_dusts.elm.trend == "steady"
assert result_dusts.elm.level == "lack"
assert result_dusts.willow.trend == "steady"
assert result_dusts.willow.level == "lack"
assert result_alerts[0] == "Wysokie stężenie pyłku olszy, bardzo niskie leszczyny."


@pytest.mark.asyncio
Expand Down Expand Up @@ -135,7 +87,7 @@ async def test_api_error():
)
zadnegoale = ZadnegoAle(session, VALID_REGION)
try:
await zadnegoale.async_update()
await zadnegoale.async_get_dusts()
except ApiError as error:
assert str(error.status) == "Invalid response from Zadnego Ale API: 404"

Expand All @@ -156,7 +108,7 @@ async def test_invalid_data():
)
zadnegoale = ZadnegoAle(session, VALID_REGION)
try:
await zadnegoale.async_update()
await zadnegoale.async_get_dusts()
except ApiError as error:
assert str(error.status) == "Invalid response from Zadnego Ale API: null"

Expand Down
89 changes: 50 additions & 39 deletions zadnegoale/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,28 @@
"""
import logging
from datetime import date
from typing import Any, Dict, Optional
from typing import Any, List, Optional

from aiohttp import ClientSession

from .const import ATTR_ALERTS, ATTR_DUSTS, ENDPOINT, HTTP_OK, URLS
from dacite import from_dict

from .const import (
ATTR_ALERTS,
ATTR_DUSTS,
ATTR_LEVEL,
ATTR_TREND,
ATTR_VALUE,
ENDPOINT,
HTTP_OK,
TRANSLATE_ALLERGENS_MAP,
TRANSLATE_STATES_MAP,
URL,
)
from .model import Allergens

_LOGGER = logging.getLogger(__name__)


class DictToObj(dict):
"""Dictionary to object class."""

def __getattr__(self, name: str) -> Any:
if name in self:
return self[name]
raise AttributeError("No such attribute: " + name)


class ZadnegoAle:
"""Main class to perform Zadnego Ale API requests"""

Expand All @@ -36,31 +40,38 @@ def __init__(
self._debug = debug

@staticmethod
def _construct_url(arg: str, **kwargs: Any) -> str:
def _construct_url(data_type: str, region: int) -> str:
"""Construct Zadnego Ale API URL."""
url = ENDPOINT + URLS[arg].format(**kwargs)
date_str = date.today().strftime("%Y%m%d")
url = ENDPOINT + URL.format(data_type, date_str, region)
return url

@staticmethod
def _parse_dusts(data: list) -> Dict[str, Any]:
def _parse_dusts(data: list) -> Allergens:
"""Parse and clean dusts API response."""
parsed = DictToObj(
{
item["allergen"]["name"].lower(): {
"value": item["value"],
"trend": item["trend"].lower(),
"level": item["level"].lower(),
}
for item in data
parsed = {
item["allergen"]["name"].lower(): {
ATTR_VALUE: item[ATTR_VALUE],
ATTR_TREND: TRANSLATE_STATES_MAP.get(
item[ATTR_TREND], item[ATTR_TREND]
),
ATTR_LEVEL: TRANSLATE_STATES_MAP.get(
item[ATTR_LEVEL], item[ATTR_LEVEL]
),
}
)

return {"sensors": parsed}
for item in data
}
for pol_name, eng_name in TRANSLATE_ALLERGENS_MAP:
if pol_name in parsed:
parsed[eng_name] = parsed.pop(pol_name)
else:
parsed[eng_name] = {}
return from_dict(data_class=Allergens, data=parsed)

@staticmethod
def _parse_alerts(data: Any) -> Dict[str, Any]:
def _parse_alerts(data: Any) -> List[str]:
"""Parse and clean alerts API response."""
return {"alerts": {"value": data[0]["text"]}}
return [data[index]["text"] for index in range(len(data))]

async def _async_get_data(self, url: str) -> Any:
"""Retreive data from Zadnego Ale API."""
Expand All @@ -73,10 +84,9 @@ async def _async_get_data(self, url: str) -> Any:
raise ApiError(f"Invalid response from Zadnego Ale API: {data}")
return data

async def async_update(self, alerts: bool = False) -> DictToObj:
"""Retreive data from Zadnego Ale."""
date_str = date.today().strftime("%Y%m%d")
url = self._construct_url(ATTR_DUSTS, date=date_str, region=self._region)
async def async_get_dusts(self) -> Allergens:
"""Retreive dusts data from Zadnego Ale."""
url = self._construct_url(ATTR_DUSTS, self._region)
dusts = await self._async_get_data(url)

if self._debug:
Expand All @@ -85,16 +95,17 @@ async def async_update(self, alerts: bool = False) -> DictToObj:
if not self._region_name:
self._region_name = dusts[0]["region"]["name"]

if alerts:
url = self._construct_url(ATTR_ALERTS, date=date_str, region=self._region)
alerts = await self._async_get_data(url)
return self._parse_dusts(dusts)

if self._debug:
_LOGGER.debug(alerts)
async def async_get_alerts(self) -> List[str]:
"""Retreive dusts data from Zadnego Ale."""
url = self._construct_url(ATTR_ALERTS, self._region)
alerts = await self._async_get_data(url)

return DictToObj({**self._parse_dusts(dusts), **self._parse_alerts(alerts)})
if self._debug:
_LOGGER.debug(alerts)

return DictToObj(self._parse_dusts(dusts))
return self._parse_alerts(alerts)

@property
def region_name(self) -> Optional[str]:
Expand Down
Loading

0 comments on commit a487f75

Please sign in to comment.