• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

domdfcoding / octo-api / 14924269125

09 May 2025 07:57AM UTC coverage: 95.0% (-0.6%) from 95.556%
14924269125

push

github

web-flow
[repo-helper] Configuration Update (#47)

* Updated files with 'repo_helper'.

* Updated files with 'repo_helper'.

* Updated files with 'repo_helper'.

* Updated files with 'repo_helper'.

---------

Co-authored-by: repo-helper[bot] <74742576+repo-helper[bot]@users.noreply.github.com>

342 of 360 relevant lines covered (95.0%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.87
/octo_api/utils.py
1
#!/usr/bin/env python3
2
#
3
#  utils.py
4
"""
5
Utility functions.
6
"""
7
#
8
#  Copyright © 2020 Dominic Davis-Foster <dominic@davis-foster.co.uk>
9
#
10
#  Permission is hereby granted, free of charge, to any person obtaining a copy
11
#  of this software and associated documentation files (the "Software"), to deal
12
#  in the Software without restriction, including without limitation the rights
13
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
#  copies of the Software, and to permit persons to whom the Software is
15
#  furnished to do so, subject to the following conditions:
16
#
17
#  The above copyright notice and this permission notice shall be included in all
18
#  copies or substantial portions of the Software.
19
#
20
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
24
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
25
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
26
#  OR OTHER DEALINGS IN THE SOFTWARE.
27
#
28

29
# stdlib
30
import sys
1✔
31
import textwrap
1✔
32
from typing import Any, Dict, NamedTuple, Optional, Type, Union
1✔
33

34
# 3rd party
35
import attr
1✔
36
import prettyprinter  # type: ignore[import]
1✔
37
from domdf_python_tools.doctools import prettify_docstrings
1✔
38
from domdf_python_tools.stringlist import StringList
1✔
39
from enum_tools import StrEnum
1✔
40

41
__all__ = [
1✔
42
                "from_iso_zulu",
43
                "RateType",
44
                "Region",
45
                "MeterPointDetails",
46
                "add_repr",
47
                ]
48

49
# stdlib
50
from datetime import datetime, timedelta, timezone
1✔
51

52
if sys.version_info[:2] < (3, 7):
1✔
53
        # 3rd party
54
        from backports.datetime_fromisoformat import MonkeyPatch  # nodep
×
55
        MonkeyPatch.patch_fromisoformat()
×
56

57
# def format_datetime(dt: datetime) -> str:
58
#         """
59
#         Format a :class:`datetime.datetime` object to a string in
60
#         :wikipedia:`ISO 8601` format.
61
#
62
#         :param dt:
63
#         """
64
#
65
#         return dt.strftime("%Y-%m-%dT:")
66

67

68
def from_iso_zulu(the_datetime: Union[str, datetime, None]) -> Optional[datetime]:
1✔
69
        """
70
        Constructs a :class:`datetime.datetime` object from an
71
        `ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ format string.
72

73
        This function understands the character ``Z`` as meaning Zulu time (GMT/UTC).
74

75
        :param the_datetime:
76
        """  # noqa: D400
77

78
        if the_datetime is None:
1✔
79
                return the_datetime
1✔
80
        elif isinstance(the_datetime, datetime):
1✔
81
                return the_datetime
1✔
82
        else:
83
                return datetime.fromisoformat(the_datetime.replace('Z', "+00:00"))
1✔
84

85

86
class RateType(StrEnum):
1✔
87
        """
88
        Enumeration of different rate types.
89
        """
90

91
        StandingCharge = "standing-charges"
1✔
92
        StandardUnitRate = "standard-unit-rates"
1✔
93
        DayUnitRate = "day-unit-rates"
1✔
94
        NightUnitRate = "night-unit-rates"
1✔
95

96

97
class Region(StrEnum):
1✔
98
        """
99
        Enumeration of different electricity supply regions.
100

101
        The different regions can be seen on the following map:
102

103
        .. image:: pes_boundaries.png
104
                :width: 300
105
                :alt: Electricity Regions
106
        """
107

108
        Eastern = "_A"  # Eastern Electricity
1✔
109
        EastMidlands = "_B"  # East Midlands Electricity
1✔
110
        London = "_C"  # London Electricity
1✔
111
        Merseyside = "_D"  # Merseyside and North Wales Electricity Board
1✔
112
        NorthWales = "_D"  # Merseyside and North Wales Electricity Board
1✔
113
        Midlands = "_E"  # Midlands Electricity
1✔
114
        NorthEastern = "_F"  # North Eastern Electricity Board
1✔
115
        NorthWestern = "_G"  # North Western Electricity Board
1✔
116
        Southern = "_H"  # Southern Electric
1✔
117
        SouthEastern = "_J"  # South Eastern Electricity Board
1✔
118
        SouthWales = "_K"  # South Wales Electricity
1✔
119
        SouthWestern = "_L"  # South Western Electricity
1✔
120
        Yorkshire = "_M"  # Yorkshire Electricity
1✔
121
        SouthScotland = "_N"  # South of Scotland Electricity Board
1✔
122
        NorthScotland = "_P"  # North of Scotland Hydro Board
1✔
123

124

125
@prettify_docstrings
1✔
126
class MeterPointDetails(NamedTuple):
1✔
127
        """
128
        Information about a meter point.
129

130
        :param mpan: The meter point access number.
131
        :param gsp: The grid supply point/region that the meter point is located in.
132
        :param profile_class: The profile class of the meter point.
133

134
        * **Profile Class 1** -- Domestic Unrestricted Customers
135
        * **Profile Class 2** -- Domestic Economy 7 Customers
136
        * **Profile Class 3** -- Non-Domestic Unrestricted Customers
137
        * **Profile Class 4** -- Non-Domestic Economy 7 Customers
138
        * **Profile Class 5** -- Non-Domestic Maximum Demand (MD) Customers with a Peak Load Factor (LF) of less than 20%
139
        * **Profile Class 6** -- Non-Domestic Maximum Demand Customers with a Peak Load Factor between 20% and 30%
140
        * **Profile Class 7** -- Non-Domestic Maximum Demand Customers with a Peak Load Factor between 30% and 40%
141
        * **Profile Class 8** -- Non-Domestic Maximum Demand Customers with a Peak Load Factor over 40%
142

143
        Information from https://www.elexon.co.uk/knowledgebase/profile-classes/
144

145
        .. seealso:: `Load Profiles and their use in Electricity Settlement <https://www.elexon.co.uk/documents/training-guidance/bsc-guidance-notes/load-profiles/>`_ by Elexon
146
        """
147

148
        mpan: str
1✔
149
        gsp: Region
1✔
150
        profile_class: int
1✔
151

152
        @classmethod
1✔
153
        def _from_dict(cls, octopus_dict: Dict[str, Any]) -> "MeterPointDetails":
1✔
154
                return MeterPointDetails(
1✔
155
                                mpan=str(octopus_dict["mpan"]),
156
                                gsp=Region(octopus_dict["gsp"]),
157
                                profile_class=int(octopus_dict["profile_class"]),
158
                                )
159

160

161
#: The British Summer Time timezone (UTC+1).
162
bst = timezone(timedelta(seconds=3600))
1✔
163

164
#: The Greenwich Mean Time timezone (aka UTC).
165
gmt = timezone.utc
1✔
166

167
utc = gmt
1✔
168

169

170
def add_repr(cls: Type) -> Type:
1✔
171
        """
172
        Add a pretty-printed ``__repr__`` function to the decorated attrs class.
173

174
        :param cls:
175

176
        .. seealso:: :func:`attr_utils.pprinter.pretty_repr`.
177
        """
178

179
        if attr.has(cls):
1✔
180

181
                def __repr__(self) -> str:
1✔
182
                        buf = StringList()
1✔
183
                        buf.indent_type = "    "
1✔
184
                        buf.append(f"{self.__class__.__module__}.{self.__class__.__qualname__}(")
1✔
185

186
                        with buf.with_indent_size(1):
1✔
187
                                for attrib in attr.fields(self.__class__):
1✔
188
                                        value = getattr(self, attrib.name)
1✔
189

190
                                        if isinstance(value, datetime):
1✔
191
                                                buf.append(f"{attrib.name}={value.isoformat()!r},")
×
192

193
                                        elif isinstance(value, str):
1✔
194
                                                lines = textwrap.wrap(value, width=80 - len(attrib.name) - 1)
1✔
195
                                                buf.append(f"{attrib.name}={lines.pop(0)!r}")
1✔
196

197
                                                for line in lines:
1✔
198
                                                        buf.append(' ' * len(attrib.name) + ' ' + repr(line))
×
199

200
                                                buf[-1] = f"{buf[-1][len(buf.indent_type) * buf.indent_size:]},"
1✔
201
                                        elif value is None:
1✔
202
                                                buf.append(f"{attrib.name}=None,")
1✔
203
                                        else:
204
                                                buf.append(f"{attrib.name}={prettyprinter.pformat(value)},")
1✔
205

206
                        buf.append(')')
1✔
207
                        return str(buf)
1✔
208

209
                __repr__.__doc__ = f"Return a string representation of the :class:`~.{cls.__name__}`."
1✔
210

211
                cls.__repr__ = __repr__  # type: ignore[assignment]
1✔
212
                cls.__repr__.__qualname__ = f"{cls.__name__}.__repr__"
1✔
213
                cls.__repr__.__module__ = cls.__module__
1✔
214

215
        return cls
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc