Skip to content

Commit

Permalink
Feature/eau paris sud (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
acesyde authored Dec 4, 2023
1 parent 3107ae2 commit 8d96087
Show file tree
Hide file tree
Showing 15 changed files with 514 additions and 401 deletions.
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
repos:
- repo: https://github.com/commitizen-tools/commitizen
rev: 3.7.0
rev: 3.12.0
hooks:
- id: commitizen
- id: commitizen-branch
stages: [ push ]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.286
rev: v0.1.6
hooks:
- id: ruff
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: no-commit-to-branch
args:
Expand Down
4 changes: 3 additions & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
[![hacs][hacsbadge]][hacs]
![Project Maintenance][maintenance-shield]

_Integration to integrate with [EAU par Agur][eau_agur]._
_The following providers are supported :_
- [EAU par Agur][eau_agur]
- [EAU par Grand Paris Sud][eau_grandparissud]

🌏
[**Français**](README.md) |
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
[![hacs][hacsbadge]][hacs]
![Project Maintenance][maintenance-shield]

_Intégration pour [EAU par Agur][eau_agur]._
_Intégration pour les providers suivants :_
- [EAU par Agur][eau_agur]
- [EAU par Grand Paris Sud][eau_grandparissud]

🌏
Français |
Expand Down Expand Up @@ -48,6 +50,8 @@ Si vous souhaitez y contribuer, veuillez lire les [Directives de contribution](C

[eau_agur]: https://www.agur.fr/

[eau_grandparissud]: https://abonne-eau.grandparissud.fr/

[commits-shield]: https://img.shields.io/github/commit-activity/y/acesyde/hassio_agur_integration.svg?style=for-the-badge

[commits]: https://github.com/acesyde/hassio_agur_integration/commits/main
Expand Down
17 changes: 13 additions & 4 deletions custom_components/eau_agur/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .api import AgurApiClient
from .const import DOMAIN, PLATFORMS, COORDINATOR
from .const import CONF_PROVIDER, DOMAIN, PLATFORMS, COORDINATOR, PROVIDERS
from .coordinator import EauAgurDataUpdateCoordinator


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up EAU par Agur from a config entry."""
session = async_get_clientsession(hass)

config_provider = PROVIDERS.get(entry.data[CONF_PROVIDER], None)
if config_provider is None:
raise Exception("Provider not found")

client = AgurApiClient(
host=config_provider["base_url"],
base_path=config_provider.get("base_path", None),
timeout=config_provider.get("default_timeout", None),
conversation_id=config_provider["conversation_id"],
client_id=config_provider["client_id"],
access_key=config_provider["access_key"],
session=session,
)

Expand All @@ -29,9 +40,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Handle removal of an entry."""
if unloaded := await hass.config_entries.async_unload_platforms(
entry, PLATFORMS
):
if unloaded := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unloaded

Expand Down
109 changes: 52 additions & 57 deletions custom_components/eau_agur/api/agur_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,42 @@
from yarl import URL

from .exceptions import AgurApiConnectionError, AgurApiError, AgurApiUnauthorizedError
from .const import BASE_URL, DEFAULT_TIMEOUT, ACCESS_KEY, CLIENT_ID, CONVERSATION_ID, LOGIN_PATH, \
GENERATE_TOKEN_PATH, GET_DEFAULT_CONTRACT_PATH, GET_CONSUMPTION_PATH, BASE_PATH, LOGGER
from .const import (
BASE_URL,
DEFAULT_TIMEOUT,
ACCESS_KEY,
CLIENT_ID,
CONVERSATION_ID,
LOGIN_PATH,
GENERATE_TOKEN_PATH,
GET_DEFAULT_CONTRACT_PATH,
GET_CONSUMPTION_PATH,
BASE_PATH,
LOGGER,
)


class AgurApiClient:
"""Main class for handling connections with the Agur API."""

def __init__(
self,
host: str = BASE_URL,
base_path: str = BASE_PATH,
timeout: int = DEFAULT_TIMEOUT,
conversation_id: str = CONVERSATION_ID,
client_id: str = CLIENT_ID,
access_key: str = ACCESS_KEY,
session: aiohttp.ClientSession | None = None,
self,
host: str = BASE_URL,
base_path: str | None = BASE_PATH,
timeout: int | None = DEFAULT_TIMEOUT,
conversation_id: str = CONVERSATION_ID,
client_id: str = CLIENT_ID,
access_key: str = ACCESS_KEY,
session: aiohttp.ClientSession | None = None,
) -> AgurApiClient:
"""Initialize connection with the Agur API."""

if base_path is None:
base_path = BASE_PATH

if timeout is None:
timeout = DEFAULT_TIMEOUT

self._token = None
self._session = session
self._close_session = False
Expand All @@ -45,19 +62,17 @@ def __init__(
self._base_path += "/"

async def request(
self,
uri: str,
method: str = "GET",
data: Any | None = None,
json_data: dict | None = None,
headers: dict[str, str] | None = None,
params: Mapping[str, str] | None = None,
self,
uri: str,
method: str = "GET",
data: Any | None = None,
json_data: dict | None = None,
headers: dict[str, str] | None = None,
params: Mapping[str, str] | None = None,
) -> dict[str, Any]:
"""Make a request to the Agur API."""

url = URL.build(
scheme="https", host=self._host, path=self._base_path
).join(URL(uri))
url = URL.build(scheme="https", host=self._host, path=self._base_path).join(URL(uri))

LOGGER.debug("URL: %s", url)

Expand Down Expand Up @@ -85,26 +100,18 @@ async def request(
headers=headers,
)
except asyncio.TimeoutError as exception:
raise AgurApiConnectionError(
"Timeout occurred while connecting to Agur API."
) from exception
raise AgurApiConnectionError("Timeout occurred while connecting to Agur API.") from exception
except (aiohttp.ClientError, socket.gaierror) as exception:
raise AgurApiConnectionError(
"Error occurred while communicating with Agur API."
) from exception
raise AgurApiConnectionError("Error occurred while communicating with Agur API.") from exception

content_type = response.headers.get("Content-Type", "")
if response.status // 100 in [4, 5]:
contents = await response.read()
response.close()

if content_type == "application/json":
raise AgurApiError(
response.status, json.loads(contents.decode("utf8"))
)
raise AgurApiError(
response.status, {"message": contents.decode("utf8")}
)
raise AgurApiError(response.status, json.loads(contents.decode("utf8")))
raise AgurApiError(response.status, {"message": contents.decode("utf8")})

if "application/json" in content_type:
return await response.json()
Expand All @@ -124,15 +131,14 @@ async def generate_temporary_token(self) -> None:
json_data={
"AccessKey": self._access_key,
"ClientId": self._client_id,
"ConversationId": self._conversation_id
})
"ConversationId": self._conversation_id,
},
)

self._token = response["token"]

except AgurApiError as exception:
raise AgurApiError(
"Error occurred while generating temporary token."
) from exception
raise AgurApiError("Error occurred while generating temporary token.") from exception

async def login(self, email: str, password: str) -> bool:
"""Login to Agur API."""
Expand All @@ -143,47 +149,36 @@ async def login(self, email: str, password: str) -> bool:
json_data={
"identifiant": email,
"motDePasse": password,
})
},
)

self._token = response["tokenAuthentique"]

except AgurApiError as exception:
if exception.status == 401:
raise AgurApiUnauthorizedError(
"Invalid credentials."
) from exception
if exception.args[0] == 401:
raise AgurApiUnauthorizedError("Invalid credentials.") from exception

raise AgurApiError(
"Error occurred while logging in."
) from exception
raise AgurApiError("Error occurred while logging in.") from exception

async def get_default_contract(self) -> str:
"""Get default contract."""
try:
response = await self.request(
uri=GET_DEFAULT_CONTRACT_PATH,
method="GET")
response = await self.request(uri=GET_DEFAULT_CONTRACT_PATH, method="GET")

return response["numeroContrat"]

except AgurApiError as exception:
raise AgurApiError(
"Error occurred while getting default contract."
) from exception
raise AgurApiError("Error occurred while getting default contract.") from exception

async def get_consumption(self, contract_id: str) -> float:
"""Get consumption."""
try:
response = await self.request(
f"{GET_CONSUMPTION_PATH}{contract_id}",
"GET")
response = await self.request(f"{GET_CONSUMPTION_PATH}{contract_id}", "GET")

return response["valeurIndex"]

except AgurApiError as exception:
raise AgurApiError(
"Error occurred while getting consumption."
) from exception
raise AgurApiError("Error occurred while getting consumption.") from exception

async def __aenter__(self) -> Any:
"""Async enter.
Expand Down
1 change: 0 additions & 1 deletion custom_components/eau_agur/api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
class AgurApiError(Exception):
"""Generic Agur API exception."""


class AgurApiConnectionError(AgurApiError):
"""Agur API connection exception."""

Expand Down
54 changes: 36 additions & 18 deletions custom_components/eau_agur/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from homeassistant.helpers import selector

from .api import AgurApiClient, AgurApiError, AgurApiConnectionError, AgurApiUnauthorizedError
from .const import DOMAIN, LOGGER, CONF_CONTRACT_NUMBER
from .const import DOMAIN, LOGGER, CONF_CONTRACT_NUMBER, CONF_PROVIDER, PROVIDERS


class EauAgurFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
Expand All @@ -19,10 +19,20 @@ class EauAgurFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):

async def async_step_user(self, user_input=None) -> config_entries.FlowResult:
"""Handle a flow initialized by the user."""
_errors = {}
_errors: dict[str, str] = {}
if user_input is not None:
try:
config_provider = PROVIDERS.get(user_input[CONF_PROVIDER], None)
if config_provider is None:
raise AgurApiError("Provider not found")

api_client = AgurApiClient(
host=config_provider["base_url"],
base_path=config_provider.get("base_path", None),
timeout=config_provider.get("default_timeout", None),
conversation_id=config_provider["conversation_id"],
client_id=config_provider["client_id"],
access_key=config_provider["access_key"],
session=async_create_clientsession(self.hass),
)

Expand All @@ -38,12 +48,11 @@ async def async_step_user(self, user_input=None) -> config_entries.FlowResult:
data = {
CONF_EMAIL: user_input[CONF_EMAIL],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_PROVIDER: user_input[CONF_PROVIDER],
CONF_CONTRACT_NUMBER: default_contract_id,
}

await self.async_set_unique_id(
default_contract_id
)
await self.async_set_unique_id(default_contract_id)

self._abort_if_unique_id_configured()

Expand All @@ -64,18 +73,27 @@ async def async_step_user(self, user_input=None) -> config_entries.FlowResult:

return self.async_show_form(
step_id="user",
data_schema=vol.Schema({
vol.Required(
CONF_EMAIL,
default=(user_input or {}).get(CONF_EMAIL),
): selector.TextSelector(
selector.TextSelectorConfig(
type=selector.TextSelectorType.EMAIL
data_schema=vol.Schema(
{
vol.Required(
CONF_EMAIL,
default=(user_input or {}).get(CONF_EMAIL),
): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.EMAIL),
),
),
vol.Required(CONF_PASSWORD): selector.TextSelector(
selector.TextSelectorConfig(
type=selector.TextSelectorType.PASSWORD
vol.Required(CONF_PASSWORD): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD),
),
),
}))
vol.Required(CONF_PROVIDER): selector.SelectSelector(
selector.SelectSelectorConfig(
options=[
selector.SelectOptionDict(value=key, label=PROVIDERS[key]["display_name"])
for key in PROVIDERS
],
mode=selector.SelectSelectorMode.DROPDOWN,
),
),
}
),
errors=_errors,
)
Loading

0 comments on commit 8d96087

Please sign in to comment.