Skip to content

Commit

Permalink
Add get consumption to the opinionated client
Browse files Browse the repository at this point in the history
  • Loading branch information
markallanson committed Jan 30, 2021
1 parent 8497dbf commit 66214b8
Show file tree
Hide file tree
Showing 13 changed files with 420 additions and 43 deletions.
141 changes: 141 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Octopus Energy Specifics
integ_tests/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/
2 changes: 2 additions & 0 deletions octopus_energy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
UnitType,
ElectricityMeter,
GasMeter,
PageReference,
)
from .exceptions import ApiAuthenticationError, ApiError, ApiNotFoundError, ApiBadRequestError
from .rest_client import OctopusEnergyRestClient
Expand Down Expand Up @@ -45,4 +46,5 @@
"Address",
"ElectricityMeter",
"GasMeter",
"PageReference",
]
54 changes: 52 additions & 2 deletions octopus_energy/client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from datetime import datetime
from typing import List

from .mappers import meters_from_response
from octopus_energy import Meter, OctopusEnergyRestClient
from .mappers import meters_from_response, consumption_from_response
from octopus_energy import (
Meter,
OctopusEnergyRestClient,
Consumption,
EnergyType,
SortOrder,
PageReference,
)


class OctopusEnergyConsumerClient:
Expand Down Expand Up @@ -49,3 +57,45 @@ async def close(self):
async def get_meters(self) -> List[Meter]:
"""Gets all meters associated with your account."""
return meters_from_response(await self.rest_client.get_account_details(self.account_number))

async def get_consumption(
self,
meter: Meter,
period_from: datetime = None,
period_to: datetime = None,
page_reference: PageReference = None,
) -> Consumption:
"""Get the energy consumption for a meter
Args:
meter: The meter to get consumption for.
period_from: The timestamp for the earliest period of consumption to return.
period_to: The timestamp for the latest period of consumption to return.
page_reference: Get a specific page of results based on a page reference returned by
a previous call to get_consumption
Returns:
The consumption for the meter in the time period specified. The results are returned
in ascending timestamp order from the start of the period.
"""
func = (
self.rest_client.get_electricity_consumption_v1
if meter.energy_type == EnergyType.ELECTRICITY
else self.rest_client.get_gas_consumption_v1
)

params = {}
if page_reference:
params.update(page_reference.options)
else:
params.update(
{
"period_from": period_from,
"period_to": period_to,
"order": SortOrder.OLDEST_FIRST,
}
)

response = await func(meter.meter_point.id, meter.serial_number, **params)
return consumption_from_response(response, meter)
40 changes: 35 additions & 5 deletions octopus_energy/mappers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import List, Optional

from datetime import datetime
from typing import List, Optional

import dateutil
from dateutil.parser import isoparse
from furl import furl

from .models import (
IntervalConsumption,
Expand All @@ -18,6 +18,9 @@
Meter,
Address,
MeterPoint,
PageReference,
SortOrder,
Aggregate,
)

_CUBIC_METERS_TO_KWH_MULTIPLIER = 11.1868
Expand Down Expand Up @@ -103,7 +106,7 @@ def meters_from_response(response: dict) -> List[Meter]:


def consumption_from_response(
response: dict, meter: Meter, desired_unit_type: UnitType
response: dict, meter: Meter, desired_unit_type: UnitType = None
) -> Consumption:
"""Generates the Consumption model from an octopus energy API response.
Expand Down Expand Up @@ -132,10 +135,33 @@ def consumption_from_response(
)
for result in response["results"]
],
_get_page_reference(response, "previous"),
_get_page_reference(response, "next"),
)


def _calculate_unit(consumption, actual_unit, desired_unit):
def _get_page_reference(response: dict, page: str):
if page not in response:
return None
page_url = furl(response[page])
if not page_url.args:
return None

# Convert all args in the page reference to the types used by the APIs
args = dict(page_url.args)
if "period_from" in args:
args["period_from"] = from_timestamp_str(args["period_from"])
if "period_to" in args:
args["period_to"] = from_timestamp_str(args["period_to"])
if "order" in args:
args["order"] = SortOrder(args["order"])
if "group_by" in args:
args["group_by"] = Aggregate(args["group_by"])

return PageReference(args)


def _calculate_unit(consumption, actual_unit, desired_unit: UnitType = None):
"""Converts unit values from one unit to another unit.
If no mapping is available the value is returned unchanged.
Expand All @@ -145,4 +171,8 @@ def _calculate_unit(consumption, actual_unit, desired_unit):
:param desired_unit: The unit the convert the consumption to.
:return: The consumption converted to the desired unit.
"""
return consumption * _UNIT_MULTIPLIERS.get((actual_unit.value, desired_unit.value), 1)
return (
consumption
if not desired_unit
else consumption * _UNIT_MULTIPLIERS.get((actual_unit.value, desired_unit.value), 1)
)
23 changes: 15 additions & 8 deletions octopus_energy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ def __new__(cls, value, doc):
return self


@dataclass
class PageReference:
"""Represents a reference to a page of information for API calls that support paging"""

options: dict


@dataclass
class Tariff:
code: str
Expand Down Expand Up @@ -64,9 +71,6 @@ def description(self) -> str:
"""A description, in english, of the meter."""
return self.value[2]

def __eq__(self, other):
return self.value == other.value


class EnergyType(Enum):
"""Represents a type of energy."""
Expand Down Expand Up @@ -95,11 +99,12 @@ class Address:
def __str__(self):
"""Gets a single line string representation of the address"""
return (
f"{self.line_1} "
f"{self.line_2 + ', ' if self.line_2 is not None else ''} "
f"{self.line_3 + ', ' if self.line_3 is not None else ''}"
f"{self.county + ', ' if self.county is not None else ''}"
f"{self.town + ', ' if self.town is not None else ''}"
f"{self.line_1}"
f"{', ' + self.line_2 if self.line_2 is not None else ''}"
f"{', ' + self.line_3 if self.line_3 is not None else ''}"
f"{', ' + self.county if self.county is not None else ''}"
f"{', ' + self.town if self.town is not None else ''}"
f"{', ' + self.postcode if self.postcode is not None else ''}"
)


Expand Down Expand Up @@ -165,6 +170,8 @@ class Consumption:
unit_type: UnitType
meter: Meter
intervals: List[IntervalConsumption] = field(default_factory=lambda: [])
previous_page: Optional[PageReference] = None
next_page: Optional[PageReference] = None


class EnergyTariffType(_DocEnum):
Expand Down
Loading

0 comments on commit 66214b8

Please sign in to comment.