diff --git a/README.md b/README.md index 756990ea..e8e6a0bf 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ [![Maintainability][maintainability-shield]][maintainability-url] [![Code Coverage][codecov-shield]][codecov-url] -Asynchronous Python client for the Autarco Inverters. +Asynchronous Python client for the Autarco Inverters (External API). ## About @@ -32,8 +32,7 @@ The data on the platform is updated every 5 minutes. You can find this in the url after logging in, example: `https://my.autarco.com/site/{public_key}` -Or by using the `get_public_key` method, that will -return your key. +Or by using the `get_account` function, and use the `public_key` attribute. ## Installation @@ -55,11 +54,11 @@ async def main(): email="test@autarco.com", password="password", ) as client: - public_key = await client.get_public_key() + account = await client.get_account() - inverters = await client.all_inverters(public_key) - solar = await client.solar(public_key) - account = await client.account(public_key) + inverters = await client.get_inverters(account.public_key) + solar = await client.get_solar(account.public_key) + location = await client.get_location(account.public_key) print(inverters) print(solar) print(account) @@ -69,15 +68,25 @@ if __name__ == "__main__": asyncio.run(main()) ``` +More examples can be found in the [examples folder](./examples/). + ## Data You can read the following data with this package: +### Account + +- Public Key +- System Name +- Retailer Name +- Health Status + ### Inverter(s) - Serial Number -- Current Power Production (W) -- Total Energy Production (kWh) +- Out AC - Power (W) +- Out AC - Energy Total (kWh) +- Grid Turned Off (bool) - Health Status ### Solar @@ -87,16 +96,16 @@ You can read the following data with this package: - Energy Production - Month (kWh) - Energy Production - Total (kWh) -### Account +### Location -- Public Key (str) -- Name (str) +- Public Key +- Name - Address (dict) - - Street (str) - - Zip Code (str) - - City (str) - - Country (str) -- Timezone (str) + - Street + - Zip Code + - City + - Country +- Timezone - Created At (date) - Has consumption meter (bool) - Has battery (bool) diff --git a/examples/account.py b/examples/account.py index be8e86a9..3b49825b 100644 --- a/examples/account.py +++ b/examples/account.py @@ -2,7 +2,7 @@ import asyncio -from autarco import Autarco +from autarco import Account, Autarco async def main() -> None: @@ -11,8 +11,7 @@ async def main() -> None: email="test@autarco.com", password="password", ) as client: - public_key = await client.get_public_key() - account = await client.get_account(public_key) + account: Account = await client.get_account() print(account) diff --git a/examples/all_output.py b/examples/all_output.py index d715fcca..d2294ccf 100644 --- a/examples/all_output.py +++ b/examples/all_output.py @@ -2,7 +2,7 @@ import asyncio -from autarco import Account, Autarco, Inverter, Solar +from autarco import Account, Autarco, Inverter, Location, Solar async def main() -> None: @@ -11,19 +11,25 @@ async def main() -> None: email="test@autarco.com", password="password", ) as autarco: - public_key = await autarco.get_public_key() + account: Account = await autarco.get_account() - inverters: dict[str, Inverter] = await autarco.get_inverters(public_key) - solar: Solar = await autarco.get_solar(public_key) - account: Account = await autarco.get_account(public_key) + inverters: dict[str, Inverter] = await autarco.get_inverters(account.public_key) + solar: Solar = await autarco.get_solar(account.public_key) + location: Location = await autarco.get_location(account.public_key) - print(f"Public key: {public_key}") + print("--- ACCOUNT ---") + print(account) print() + print(f"Public Key: {account.public_key}") + print(f"Name: {account.system_name}") + print(f"Retailer: {account.retailer}") + print(f"Health: {account.health}") print("--- INVERTER(S) ---") print(inverters) print() for item in inverters.values(): + print(f"Serial Number: {item.serial_number}") print(item) print() @@ -36,14 +42,14 @@ async def main() -> None: print(f"Energy Production - Total: {solar.energy_production_total}") print() - print("--- ACCOUNT ---") - print(account) + print("--- LOCATION ---") + print(location) print() - print(f"Public Key: {account.public_key}") - print(f"Name: {account.name}") - print(f"Timezone: {account.timezone}") - print(f"City: {account.address.city}") - print(f"Country: {account.address.country}") + print(f"Address: {location.address}") + print(f"Timezone: {location.timezone}") + print(f"Created At: {location.created_at}") + print(f"Consumption Meter: {location.has_consumption_meter}") + print(f"Has Battery: {location.has_battery}") if __name__ == "__main__": diff --git a/examples/inverters.py b/examples/inverters.py index 39c12b1c..8e7626c8 100644 --- a/examples/inverters.py +++ b/examples/inverters.py @@ -2,7 +2,7 @@ import asyncio -from autarco import Autarco +from autarco import Account, Autarco async def main() -> None: @@ -11,8 +11,8 @@ async def main() -> None: email="test@autarco.com", password="password", ) as client: - public_key = await client.get_public_key() - inverters = await client.get_inverters(public_key) + account: Account = await client.get_account() + inverters = await client.get_inverters(account.public_key) print(inverters) diff --git a/examples/location.py b/examples/location.py new file mode 100644 index 00000000..9e667fb1 --- /dev/null +++ b/examples/location.py @@ -0,0 +1,20 @@ +"""Asynchronous Python client for the Autarco API.""" + +import asyncio + +from autarco import Account, Autarco + + +async def main() -> None: + """Show example on getting Autarco - location data.""" + async with Autarco( + email="test@autarco.com", + password="password", + ) as client: + account: Account = await client.get_account() + location = await client.get_location(account.public_key) + print(location) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/solar.py b/examples/solar.py index 188adaf5..e044c8f7 100644 --- a/examples/solar.py +++ b/examples/solar.py @@ -2,7 +2,7 @@ import asyncio -from autarco import Autarco +from autarco import Account, Autarco async def main() -> None: @@ -11,8 +11,8 @@ async def main() -> None: email="test@autarco.com", password="password", ) as client: - public_key = await client.get_public_key() - solar = await client.get_solar(public_key) + account: Account = await client.get_account() + solar = await client.get_solar(account.public_key) print(solar) diff --git a/src/autarco/__init__.py b/src/autarco/__init__.py index 04559cec..c22bb668 100644 --- a/src/autarco/__init__.py +++ b/src/autarco/__init__.py @@ -6,7 +6,7 @@ AutarcoConnectionError, AutarcoError, ) -from .models import Account, DateStrategy, Inverter, Solar +from .models import Account, DateStrategy, Inverter, Location, Solar __all__ = [ "Account", @@ -16,5 +16,6 @@ "AutarcoError", "DateStrategy", "Inverter", + "Location", "Solar", ] diff --git a/src/autarco/autarco.py b/src/autarco/autarco.py index cd2f8128..b283cbea 100644 --- a/src/autarco/autarco.py +++ b/src/autarco/autarco.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio -import json import socket from dataclasses import dataclass from importlib import metadata @@ -18,7 +17,15 @@ AutarcoConnectionError, AutarcoError, ) -from .models import Account, EnergyResponse, Inverter, PowerResponse, Solar +from .models import ( + Account, + AccountResponse, + EnergyResponse, + Inverter, + Location, + PowerResponse, + Solar, +) VERSION = metadata.version(__package__) @@ -121,18 +128,16 @@ async def _request( return text - async def get_public_key(self) -> str: - """Get the public key. + async def get_account(self) -> Account: + """Get information about the account. Returns ------- - The public key as string. + An Account object. Note: it returns the first account found. """ response = await self._request("") - data = json.loads(response) - public_key: str = data[0]["public_key"] - return public_key + return AccountResponse.from_json(response).data[0] async def get_inverters(self, public_key: str) -> dict[str, Inverter]: """Get a list of all used inverters. @@ -169,8 +174,8 @@ async def get_solar(self, public_key: str) -> Solar: combined = {**power_class.stats["kpis"], **energy_class.stats["kpis"]} return Solar.from_dict(combined) - async def get_account(self, public_key: str) -> Account: - """Get information about your account. + async def get_location(self, public_key: str) -> Location: + """Get information about your system location. Args: ---- @@ -178,11 +183,11 @@ async def get_account(self, public_key: str) -> Account: Returns: ------- - An Account object. + An Location object. """ response = await self._request(f"{public_key}/") - return Account.from_json(response) + return Location.from_json(response) async def close(self) -> None: """Close open client session.""" diff --git a/src/autarco/models.py b/src/autarco/models.py index df9f1def..1f4258bc 100644 --- a/src/autarco/models.py +++ b/src/autarco/models.py @@ -26,19 +26,26 @@ def deserialize(self, value: str) -> date: @dataclass class PowerResponse(DataClassORJSONMixin): - """Object representing an Inverter model response from the API.""" + """Object representing an Power Response model from the API.""" inverters: dict[str, Inverter] - stats: dict[str, dict[str, int]] + stats: dict[str, dict[str, Any]] @dataclass class EnergyResponse(DataClassORJSONMixin): - """Object representing an Inverter model response from the API.""" + """Object representing an Energy Response model response from the API.""" stats: dict[str, dict[str, Any]] +@dataclass +class AccountResponse(DataClassORJSONMixin): + """Object representing an Account model response from the API.""" + + data: list[Account] + + @dataclass class Inverter(DataClassORJSONMixin): """Object representing an Inverter model response from the API.""" @@ -62,7 +69,17 @@ class Solar(DataClassDictMixin): @dataclass class Account(DataClassORJSONMixin): - """Object representing an Account model response from the API.""" + """Object representing a Account model response from the API.""" + + public_key: str + system_name: str = field(metadata=field_options(alias="name")) + retailer: str = field(metadata=field_options(alias="name_retailer")) + health: str + + +@dataclass +class Location(DataClassORJSONMixin): + """Object representing an Location model response from the API.""" # pylint: disable-next=too-few-public-methods class Config(BaseConfig): diff --git a/tests/__snapshots__/test_models.ambr b/tests/__snapshots__/test_models.ambr index f9925254..9010b5aa 100644 --- a/tests/__snapshots__/test_models.ambr +++ b/tests/__snapshots__/test_models.ambr @@ -1,6 +1,6 @@ # serializer version: 1 # name: test_get_account - Account(public_key='sd6fv516', name='My Autarco solar installation', address=Address(street='Streetname 00', zip_code='1111 AA', city='Valkenswaard', country='Nederland'), timezone='Europe/Amsterdam', created_at=datetime.date(2023, 5, 15), has_consumption_meter=False, has_battery=False) + Account(public_key='blabla', system_name='Mijn Autarco', retailer='Klaas Schoute', health='OK') # --- # name: test_get_inverters dict({ @@ -8,8 +8,8 @@ '987654321234': Inverter(serial_number='987654321234', out_ac_power=100, out_ac_energy_total=3607, grid_turned_off=False, health='OK'), }) # --- -# name: test_get_public_key - 'sd6fv516' +# name: test_get_location + Location(public_key='blabla', name='My Autarco solar installation', address=Address(street='Streetname 00', zip_code='1111 AA', city='Valkenswaard', country='Nederland'), timezone='Europe/Amsterdam', created_at=datetime.date(2023, 5, 15), has_consumption_meter=False, has_battery=False) # --- # name: test_get_solar Solar(power_production=200, energy_production_today=4, energy_production_month=58, energy_production_total=10379) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 4ad6c2c7..225de180 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -1,41 +1,32 @@ { - "public_key": "sd6fv516", - "name": "My Autarco solar installation", - "address": { - "address_line_1": "Streetname 00", - "postcode": "1111 AA", - "city": "Valkenswaard", - "country": "Nederland" - }, - "timezone": "Europe/Amsterdam", - "dt_created": "2023-05-15", - "dt_updated": "2023-05-15", - "has_consumption_meter": false, - "has_battery": false, - "systems": [ - { - "retailer": "Kiggen", - "nominal_power": 4800, - "dt_created": "2023-05-15", - "dt_updated": "2023-05-15", - "inverters": { - "1802040231290027": { - "serial_number": "1802040231290027", - "variant_code": "S2.MX4600-MIII.1", - "dt_installed": "2023-05-15", - "dt_uninstalled": null - } - }, - "layout": [ - { - "qty": 12, - "type": "S1.MHJ400B", - "pitch": 35, - "azimuth": -42 - } - ], - "guarantee": null, - "dt_installed": "2023-05-15" - } - ] + "current_page": 1, + "data": [ + { + "mock": 0, + "name_end_user": "Mijn Autarco", + "name_retailer": "Klaas Schoute", + "public_key": "blabla", + "dt_created": "2017-03-14 13:02:44", + "dt_updated": "2020-11-16 08:07:52", + "address_line_1": "Zernikedreef 00", + "address_line_2": null, + "address_line_3": null, + "city": "Leiden", + "postcode": "2333CK", + "country_code": "NL", + "country_name": "", + "site_id": 1000, + "address_id": 1000, + "nominal_power": 1000, + "health": "OK", + "name": "Mijn Autarco" + } + ], + "first_page_url": "https://my.autarco.com/api/site?page=1", + "from": 1, + "next_page_url": null, + "path": "https://my.autarco.com/api/site", + "per_page": 30, + "prev_page_url": null, + "to": 1 } diff --git a/tests/fixtures/location.json b/tests/fixtures/location.json new file mode 100644 index 00000000..cc308301 --- /dev/null +++ b/tests/fixtures/location.json @@ -0,0 +1,41 @@ +{ + "public_key": "blabla", + "name": "My Autarco solar installation", + "address": { + "address_line_1": "Streetname 00", + "postcode": "1111 AA", + "city": "Valkenswaard", + "country": "Nederland" + }, + "timezone": "Europe/Amsterdam", + "dt_created": "2023-05-15", + "dt_updated": "2023-05-15", + "has_consumption_meter": false, + "has_battery": false, + "systems": [ + { + "retailer": "Kiggen", + "nominal_power": 4800, + "dt_created": "2023-05-15", + "dt_updated": "2023-05-15", + "inverters": { + "1802040231290027": { + "serial_number": "1802040231290027", + "variant_code": "S2.MX4600-MIII.1", + "dt_installed": "2023-05-15", + "dt_uninstalled": null + } + }, + "layout": [ + { + "qty": 12, + "type": "S1.MHJ400B", + "pitch": 35, + "azimuth": -42 + } + ], + "guarantee": null, + "dt_installed": "2023-05-15" + } + ] +} diff --git a/tests/fixtures/public_key.json b/tests/fixtures/public_key.json deleted file mode 100644 index fc3d7a63..00000000 --- a/tests/fixtures/public_key.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "name_retailer": "Home Assistant", - "public_key": "sd6fv516", - "name": "My Autarco solar installation", - "address": "Streetname 00", - "city": "Valkenswaard", - "country": "NL", - "date": "2023-05-15" - } -] diff --git a/tests/test_autarco.py b/tests/test_autarco.py index d406b2a2..1b785f2c 100644 --- a/tests/test_autarco.py +++ b/tests/test_autarco.py @@ -14,8 +14,6 @@ AutarcoError, ) -from . import load_fixtures - async def test_json_request( aresponses: ResponsesMockServer, @@ -29,7 +27,6 @@ async def test_json_request( aresponses.Response( status=200, headers={"Content-Type": "application/json"}, - text=load_fixtures("account.json"), ), ) response = await autarco_client._request("test") @@ -38,7 +35,7 @@ async def test_json_request( async def test_internal_session(aresponses: ResponsesMockServer) -> None: - """Test JSON response is handled correctly.""" + """Test internal session is created and closed.""" aresponses.add( "my.autarco.com", "/api/site/test", @@ -46,7 +43,6 @@ async def test_internal_session(aresponses: ResponsesMockServer) -> None: aresponses.Response( status=200, headers={"Content-Type": "application/json"}, - text=load_fixtures("account.json"), ), ) async with Autarco(email="test@autarco.com", password="energy") as client: diff --git a/tests/test_models.py b/tests/test_models.py index d0583785..b99311ae 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -5,7 +5,7 @@ from aresponses import ResponsesMockServer from syrupy.assertion import SnapshotAssertion -from autarco import Account, Autarco, DateStrategy, Inverter, Solar +from autarco import Account, Autarco, DateStrategy, Inverter, Location, Solar from . import load_fixtures @@ -64,45 +64,45 @@ async def test_get_solar( assert solar == snapshot -async def test_get_account( +async def test_get_location( aresponses: ResponsesMockServer, snapshot: SnapshotAssertion, autarco_client: Autarco, ) -> None: - """Test request from a Autarco API - Account object.""" + """Test request from a Autarco API - Location object.""" aresponses.add( "my.autarco.com", "/api/site/fake_key/", "GET", aresponses.Response( - text=load_fixtures("account.json"), + text=load_fixtures("location.json"), status=200, headers={"Content-Type": "application/json; charset=utf-8"}, ), ) - account: Account = await autarco_client.get_account(public_key="fake_key") - assert account == snapshot + location: Location = await autarco_client.get_location(public_key="fake_key") + assert location == snapshot -async def test_get_public_key( +async def test_get_account( aresponses: ResponsesMockServer, snapshot: SnapshotAssertion, autarco_client: Autarco, ) -> None: - """Test request from a Autarco API - get_public_key.""" + """Test request from a Autarco API - Account object.""" aresponses.add( "my.autarco.com", "/api/site/", "GET", aresponses.Response( - text=load_fixtures("public_key.json"), + text=load_fixtures("account.json"), status=200, headers={"Content-Type": "application/json; charset=utf-8"}, ), ) - public_key = await autarco_client.get_public_key() - assert public_key == snapshot - assert public_key == "sd6fv516" + account: Account = await autarco_client.get_account() + assert account == snapshot + assert account.public_key == "blabla" def test_serialize_date() -> None: