zepben.ewb 1.0.4b1__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
zepben/ewb/__init__.py CHANGED
@@ -352,6 +352,7 @@ from zepben.ewb.services.diagram.diagram_service_comparator import DiagramServic
352
352
 
353
353
  from zepben.ewb.database.paths.database_type import *
354
354
  from zepben.ewb.database.paths.ewb_data_file_paths import *
355
+ from zepben.ewb.database.paths.local_ewb_data_file_paths import *
355
356
 
356
357
  from zepben.ewb.database.sql.column import *
357
358
  from zepben.ewb.database.sqlite.tables.sqlite_table import *
@@ -1,202 +1,89 @@
1
- # Copyright 2024 Zeppelin Bend Pty Ltd
1
+ # Copyright 2025 Zeppelin Bend Pty Ltd
2
2
  # This Source Code Form is subject to the terms of the Mozilla Public
3
3
  # License, v. 2.0. If a copy of the MPL was not distributed with this
4
4
  # file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
 
6
6
  __all__ = ['EwbDataFilePaths']
7
7
 
8
+ from abc import ABC, abstractmethod
8
9
  from datetime import date, timedelta
9
10
  from pathlib import Path
10
- from typing import Callable, Iterator, Optional, List
11
+ from typing import Optional, List, Generator
11
12
 
12
13
  from zepben.ewb import require
13
14
  from zepben.ewb.database.paths.database_type import DatabaseType
14
15
 
15
16
 
16
- class EwbDataFilePaths:
17
+ class EwbDataFilePaths(ABC):
17
18
  """Provides paths to all the various data files / folders used by EWB."""
18
19
 
19
- def __init__(self, base_dir: Path,
20
- create_path: bool = False,
21
- create_directories_func: Callable[[Path], None] = lambda it: it.mkdir(parents=True),
22
- is_directory: Callable[[Path], bool] = Path.is_dir,
23
- exists: Callable[[Path], bool] = Path.exists,
24
- list_files: Callable[[Path], Iterator[Path]] = Path.iterdir):
25
- """
26
- :param base_dir: The root directory of the EWB data structure.
27
- :param create_path: Create the root directory (and any missing parent folders) if it does not exist.
28
- """
29
- self.create_directories_func = create_directories_func
30
- self.is_directory = is_directory
31
- self.exists = exists
32
- self.list_files = list_files
33
- self._base_dir = base_dir
34
-
35
- if create_path:
36
- self.create_directories_func(base_dir)
37
-
38
- require(self.is_directory(base_dir), lambda: f"base_dir must be a directory")
39
-
40
- @property
41
- def base_dir(self):
42
- """The root directory of the EWB data structure."""
43
- return self._base_dir
44
-
45
- def customer(self, database_date: date) -> Path:
46
- """
47
- Determine the path to the "customers" database for the specified date.
48
-
49
- :param database_date: The :class:`date` to use for the "customers" database.
50
- :return: The :class:`path` to the "customers" database for the specified date.
51
- """
52
- return self._to_dated_path(database_date, DatabaseType.CUSTOMER.file_descriptor)
53
-
54
- def diagram(self, database_date: date) -> Path:
55
- """
56
- Determine the path to the "diagrams" database for the specified date.
57
-
58
- :param database_date: The :class:`date` to use for the "diagrams" database.
59
- :return: The :class:`path` to the "diagrams" database for the specified date.
60
- """
61
- return self._to_dated_path(database_date, DatabaseType.DIAGRAM.file_descriptor)
62
-
63
- def measurement(self, database_date: date) -> Path:
64
- """
65
- Determine the path to the "measurements" database for the specified date.
66
-
67
- :param database_date: The :class:`date` to use for the "measurements" database.
68
- :return: The :class:`path` to the "measurements" database for the specified date.
69
- """
70
- return self._to_dated_path(database_date, DatabaseType.MEASUREMENT.file_descriptor)
71
-
72
- def network_model(self, database_date: date) -> Path:
73
- """
74
- Determine the path to the "network model" database for the specified date.
75
-
76
- :param database_date: The :class:`date` to use for the "network model" database.
77
- :return: The :class:`path` to the "network model" database for the specified date.
78
- """
79
- return self._to_dated_path(database_date, DatabaseType.NETWORK_MODEL.file_descriptor)
80
-
81
- def tile_cache(self, database_date: date) -> Path:
82
- """
83
- Determine the path to the "tile cache" database for the specified date.
84
-
85
- :param database_date: The :class:`date` to use for the "tile cache" database.
86
- :return: The :class:`path` to the "tile cache" database for the specified date.
87
- """
88
- return self._to_dated_path(database_date, DatabaseType.TILE_CACHE.file_descriptor)
89
-
90
- def energy_reading(self, database_date: date) -> Path:
91
- """
92
- Determine the path to the "energy readings" database for the specified date.
93
-
94
- :param database_date: The :class:`date` to use for the "energy readings" database.
95
- :return: The :class:`path` to the "energy readings" database for the specified date.
96
- """
97
- return self._to_dated_path(database_date, DatabaseType.ENERGY_READING.file_descriptor)
98
-
99
- def energy_readings_index(self) -> Path:
100
- """
101
- Determine the path to the "energy readings index" database.
102
-
103
- :return: The :class:`path` to the "energy readings index" database.
104
- """
105
- return self._base_dir.joinpath(f"{DatabaseType.ENERGY_READINGS_INDEX.file_descriptor}.sqlite")
20
+ VARIANTS_PATH: str = "variants"
21
+ """
22
+ The folder containing the variants. Will be placed under the dated folder alongside the network model database.
23
+ """
106
24
 
107
- def load_aggregator_meters_by_date(self) -> Path:
25
+ def resolve(self, database_type: DatabaseType, database_date: Optional[date] = None, variant: Optional[str] = None) -> Path:
108
26
  """
109
- Determine the path to the "load aggregator meters-by-date" database.
27
+ Resolves the :class:`Path` to the database file for the specified :class:`DatabaseType`, within the specified `database_date`
28
+ and optional `variant` when `DatabaseType.per_date` is set to true.
110
29
 
111
- :return: The :class:`path` to the "load aggregator meters-by-date" database.
112
- """
113
- return self._base_dir.joinpath(f"{DatabaseType.LOAD_AGGREGATOR_METERS_BY_DATE.file_descriptor}.sqlite")
114
-
115
- def weather_reading(self) -> Path:
116
- """
117
- Determine the path to the "weather readings" database.
30
+ :param database_type: The :class:`DatabaseType` to use for the database :class:`Path`.
31
+ :param database_date: The :class:`date` to use for the database :class:`Path`. Required when `database_type.per_date` is true, otherwise must be `None`.
32
+ :param variant: The optional name of the variant containing the database.
118
33
 
119
- :return: The :class:`path` to the "weather readings" database.
34
+ :return: The :class:`Path` to the :class:`DatabaseType` database file.
120
35
  """
121
- return self._base_dir.joinpath(f"{DatabaseType.WEATHER_READING.file_descriptor}.sqlite")
122
-
123
- def results_cache(self) -> Path:
124
- """
125
- Determine the path to the "results cache" database.
126
-
127
- :return: The :class:`path` to the "results cache" database.
128
- """
129
- return self._base_dir.joinpath(f"{DatabaseType.RESULTS_CACHE.file_descriptor}.sqlite")
36
+ if database_date is not None:
37
+ require(database_type.per_date, lambda: "database_type must have its per_date set to True to use this method with a database_date.")
38
+ if variant is not None:
39
+ return self.resolve_database(self._to_dated_variant_path(database_type, database_date, variant))
40
+ else:
41
+ return self.resolve_database(self._to_dated_path(database_type, database_date))
42
+ else:
43
+ require(not database_type.per_date, lambda: "database_type must have its per_date set to False to use this method without a database_date.")
44
+ return self.resolve_database(Path(self._database_name(database_type)))
130
45
 
46
+ @abstractmethod
131
47
  def create_directories(self, database_date: date) -> Path:
132
48
  """
133
49
  Create the directories required to have a valid path for the specified date.
134
50
 
135
51
  :param database_date: The :class:`date` required in the path.
136
- :return: The :class:`path` to the directory for the `database_date`.
137
- """
138
- date_path = self._base_dir.joinpath(str(database_date))
139
- if self.exists(date_path):
140
- return date_path
141
- else:
142
- self.create_directories_func(date_path)
143
- return date_path
144
-
145
- def _to_dated_path(self, database_date: date, file: str) -> Path:
146
- return self._base_dir.joinpath(str(database_date), f"{database_date}-{file}.sqlite")
147
-
148
- def _check_exists(self, database_type: DatabaseType, database_date: date) -> bool:
52
+ :return: The :class:`Path` to the directory for the `database_date`.
149
53
  """
150
- Check if a database of the specified type and date exists.
54
+ raise NotImplemented
151
55
 
152
- :param database_type: The type of database to search for.
153
- :param database_date: The date to check.
154
- :return: `True` if a database of the specified `database_type` and `database_date` exists in the date path.
155
- """
156
- if not database_type.per_date:
157
- raise ValueError("INTERNAL ERROR: Should only be calling `checkExists` for `perDate` files.")
158
-
159
- if database_type == DatabaseType.CUSTOMER:
160
- model_path = self.customer(database_date)
161
- elif database_type == DatabaseType.DIAGRAM:
162
- model_path = self.diagram(database_date)
163
- elif database_type == DatabaseType.MEASUREMENT:
164
- model_path = self.measurement(database_date)
165
- elif database_type == DatabaseType.NETWORK_MODEL:
166
- model_path = self.network_model(database_date)
167
- elif database_type == DatabaseType.TILE_CACHE:
168
- model_path = self.tile_cache(database_date)
169
- elif database_type == DatabaseType.ENERGY_READING:
170
- model_path = self.energy_reading(database_date)
171
- else:
172
- raise ValueError(
173
- "INTERNAL ERROR: Should only be calling `check_exists` for `per_date` files, which should all be covered above, so go ahead and add it.")
174
- return self.exists(model_path)
175
-
176
- def find_closest(self, database_type: DatabaseType, max_days_to_search: int = 999, target_date: date = date.today(), search_forwards: bool = False) -> \
177
- Optional[date]:
56
+ def find_closest(
57
+ self,
58
+ database_type: DatabaseType,
59
+ max_days_to_search: int = 999999,
60
+ target_date: date = date.today(),
61
+ search_forwards: bool = False
62
+ ) -> Optional[date]:
178
63
  """
179
64
  Find the closest date with a usable database of the specified type.
180
65
 
181
66
  :param database_type: The type of database to search for.
182
67
  :param max_days_to_search: The maximum number of days to search for a valid database.
183
- :param target_date: The target :class:`date`. Defaults to today.
184
- :param search_forwards: Indicates the search should also look forwards in time from `start_date` for a valid file. Defaults to reverse search only.
185
- :return: The closest :class:`date` to `database_date` with a valid database of `database_type` within the search parameters, or `None` if no valid database was found.
68
+ :param target_date: The target date. Defaults to today.
69
+ :param search_forwards: Indicates the search should also look forwards in time from `target_date` for a valid file. Defaults to reverse search only.
70
+
71
+ :return: The closest :class:`date` to `target_date` with a valid database of `database_type` within the search parameters, or null if no valid database
72
+ was found.
186
73
  """
187
74
  if not database_type.per_date:
188
75
  return None
189
76
 
190
- if self._check_exists(database_type, target_date):
77
+ descendants = list(self.enumerate_descendants())
78
+ if self._check_exists(descendants, database_type, target_date):
191
79
  return target_date
192
80
 
193
81
  offset = 1
194
-
195
82
  while offset <= max_days_to_search:
196
83
  offset_days = timedelta(offset)
197
84
  try:
198
85
  previous_date = target_date - offset_days
199
- if self._check_exists(database_type, previous_date):
86
+ if self._check_exists(descendants, database_type, previous_date):
200
87
  return previous_date
201
88
  except OverflowError:
202
89
  pass
@@ -204,34 +91,102 @@ class EwbDataFilePaths:
204
91
  if search_forwards:
205
92
  try:
206
93
  forward_date = target_date + offset_days
207
- if self._check_exists(database_type, forward_date):
94
+ if self._check_exists(descendants, database_type, forward_date):
208
95
  return forward_date
209
96
  except OverflowError:
210
97
  pass
98
+
211
99
  offset += 1
100
+
212
101
  return None
213
102
 
214
- def _get_available_dates_for(self, database_type: DatabaseType) -> List[date]:
103
+ def get_available_dates_for(self, database_type: DatabaseType) -> List[date]:
104
+ """
105
+ Find available databases specified by :class:`DatabaseType` in data path.
106
+
107
+ :param database_type: The type of database to search for.
108
+
109
+ :return: list of :class:`date`'s for which this specified :class:`DatabaseType` databases exist in the data path.
110
+ """
215
111
  if not database_type.per_date:
216
112
  raise ValueError(
217
- "INTERNAL ERROR: Should only be calling `_get_available_dates_for` for `per_date` files.")
113
+ "INTERNAL ERROR: Should only be calling `get_available_dates_for` for `per_date` files, "
114
+ "which should all be covered above, so go ahead and add it."
115
+ )
218
116
 
219
117
  to_return = list()
220
118
 
221
- for file in self.list_files(self._base_dir):
222
- if self.is_directory(file):
119
+ for it in self.enumerate_descendants():
120
+ if it.name.endswith(self._database_name(database_type)):
223
121
  try:
224
- database_date = date.fromisoformat(file.name)
225
- if self.exists(self._to_dated_path(database_date, database_type.file_descriptor)):
226
- to_return.append(database_date)
122
+ to_return.append(date.fromisoformat(it.parent.name))
227
123
  except ValueError:
228
124
  pass
125
+
126
+ return sorted(to_return)
127
+
128
+ def get_available_variants_for(self, target_date: date = date.today()) -> List[str]:
129
+ """
130
+ Find available variants for the specified `target_date` in data path.
131
+
132
+ :param target_date: The target date. Defaults to today.
133
+
134
+ :return: list of variant names that exist in the data path for the specified `target_date`.
135
+ """
136
+ to_return = list()
137
+
138
+ for it in self.enumerate_descendants():
139
+ try:
140
+ if (str(it.parent.name).lower() == self.VARIANTS_PATH) and (str(it.parent.parent.name) == str(target_date)):
141
+ to_return.append(str(it.name))
142
+ except ValueError:
143
+ pass
144
+
229
145
  return sorted(to_return)
230
146
 
231
- def get_network_model_databases(self) -> List[date]:
147
+ @abstractmethod
148
+ def enumerate_descendants(self) -> Generator[Path, None, None]:
149
+ """
150
+ Lists the child items of source location.
151
+
152
+ :return: generator of child items.
153
+ """
154
+ raise NotImplemented
155
+
156
+ @abstractmethod
157
+ def resolve_database(self, path: Path) -> Path:
232
158
  """
233
- Find available network-model databases in data path.
159
+ Resolves the database in the specified source :class:`Path`.
234
160
 
235
- :return: A list of :class:`date`'s for which network-model databases exist in the data path.
161
+ :param path: :class:`Path` to the source database file.
162
+ :return: :class:`Path` to the local database file.
236
163
  """
237
- return self._get_available_dates_for(DatabaseType.NETWORK_MODEL)
164
+ raise NotImplemented
165
+
166
+ def _check_exists(self, descendants: List[Path], database_type: DatabaseType, database_date: date) -> bool:
167
+ """
168
+ Check if a database :class:`Path` of the specified :class:`DatabaseType` and :class:`date` exists.
169
+
170
+ :param descendants: A list of :class:`Path` representing the descendant paths.
171
+ :param database_type: The type of database to search for.
172
+ :param database_date: The date to check.
173
+
174
+ :return: True if a database of the specified `database_type` and `database_date` exits in the date path.
175
+ """
176
+ for cp in descendants:
177
+ if cp.is_relative_to(self._to_dated_path(database_type, database_date)):
178
+ return True
179
+
180
+ return False
181
+
182
+ def _to_dated_path(self, database_type: DatabaseType, database_date: date) -> Path:
183
+ date_str = str(database_date)
184
+ return Path(date_str).joinpath(f"{date_str}-{self._database_name(database_type)}")
185
+
186
+ def _to_dated_variant_path(self, database_type: DatabaseType, database_date: date, variant: str) -> Path:
187
+ date_str = str(database_date)
188
+ return Path(date_str).joinpath(self.VARIANTS_PATH, variant, f"{date_str}-{self._database_name(database_type)}")
189
+
190
+ @staticmethod
191
+ def _database_name(database_type: DatabaseType) -> str:
192
+ return f"{database_type.file_descriptor}.sqlite"
@@ -0,0 +1,58 @@
1
+ # Copyright 2025 Zeppelin Bend Pty Ltd
2
+ # This Source Code Form is subject to the terms of the Mozilla Public
3
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+
6
+ __all__ = ['LocalEwbDataFilePaths']
7
+
8
+ from datetime import date
9
+ from pathlib import Path
10
+ from typing import Callable, Generator, Union
11
+
12
+ from zepben.ewb import require
13
+ from zepben.ewb.database.paths.ewb_data_file_paths import EwbDataFilePaths
14
+
15
+
16
+ class LocalEwbDataFilePaths(EwbDataFilePaths):
17
+ """Provides paths to all the various data files / folders in the local file system used by EWB."""
18
+
19
+ def __init__(
20
+ self,
21
+ base_dir: Union[Path, str],
22
+ create_path: bool = False,
23
+ create_directories_func: Callable[[Path], None] = lambda it: it.mkdir(parents=True),
24
+ is_directory: Callable[[Path], bool] = Path.is_dir,
25
+ exists: Callable[[Path], bool] = Path.exists,
26
+ list_files: Callable[[Path], Generator[Path, None, None]] = Path.iterdir,
27
+ ):
28
+ """
29
+ :param base_dir: The root directory of the EWB data structure.
30
+ :param create_path: Create the root directory (and any missing parent folders) if it does not exist.
31
+ :param create_directories_func: Function for directory creation.
32
+ :param is_directory: Function to determine if the supplied path is a directory .
33
+ :param exists: Function to determine if the supplied path exists.
34
+ :param list_files: Function for listing directories and files under the supplied path.
35
+ """
36
+ self._base_dir = Path(base_dir)
37
+ self._create_directories_func = create_directories_func
38
+ self._exists = exists
39
+ self._list_files = list_files
40
+
41
+ if create_path:
42
+ self._create_directories_func(base_dir)
43
+
44
+ require(is_directory(base_dir), lambda: f"base_dir must be a directory")
45
+
46
+ def create_directories(self, database_date: date) -> Path:
47
+ date_path = self._base_dir.joinpath(str(database_date))
48
+ if not self._exists(date_path):
49
+ self._create_directories_func(date_path)
50
+
51
+ return date_path
52
+
53
+ def enumerate_descendants(self) -> Generator[Path, None, None]:
54
+ for it in self._list_files(self._base_dir):
55
+ yield it
56
+
57
+ def resolve_database(self, path: Path) -> Path:
58
+ return self._base_dir.joinpath(path)
@@ -84,16 +84,20 @@ class BaseDatabaseReader(ABC):
84
84
 
85
85
  return True
86
86
 
87
- async def load(self) -> bool:
87
+ async def load(self, perform_after_read_processing: bool = True) -> bool:
88
88
  """
89
- Load the database.
89
+ Read the database.
90
+
91
+ :param perform_after_read_processing: An optional "opt-out" control for performing the "after read processing".
92
+
93
+ :return: `True` if the database was successfully read, otherwise `False`.
90
94
  """
91
95
  try:
92
96
  if self._has_been_used:
93
97
  raise ValueError("You can only use the database reader once.")
94
98
  self._has_been_used = True
95
99
 
96
- return self._pre_load() and self._load_from_readers() and await self._post_load()
100
+ return self._pre_load() and self._load_from_readers() and ((not perform_after_read_processing) or await self._post_load())
97
101
  except Exception as e:
98
102
  self._logger.exception(f"Unable to load database: {e}")
99
103
  return False
@@ -83,6 +83,8 @@ class ControlledAppliance:
83
83
  if len(appliances) > 1:
84
84
  _ = [acc := f(acc, app) for app in appliances[1:]]
85
85
  self._bitmask = acc
86
+ else:
87
+ raise TypeError('appliances must be a int or Appliance')
86
88
 
87
89
  @property
88
90
  def bitmask(self):
@@ -14,7 +14,7 @@ from typing import Callable, Any, List, Generator, Optional, overload, TypeVar
14
14
  from zepben.ewb.dataclassy import dataclass
15
15
  from zepben.ewb.model.cim.iec61970.base.core.name import Name
16
16
  from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType
17
- from zepben.ewb.util import require, CopyableUUID, nlen, ngen, safe_remove
17
+ from zepben.ewb.util import require, nlen, ngen, safe_remove
18
18
 
19
19
  logger = logging.getLogger(__name__)
20
20
 
@@ -30,7 +30,7 @@ class IdentifiedObject(object, metaclass=ABCMeta):
30
30
  relation, however must be in snake case to keep the phases PEP compliant.
31
31
  """
32
32
 
33
- mrid: str = CopyableUUID()
33
+ mrid: str
34
34
  """Master resource identifier issued by a model authority. The mRID is unique within an exchange context.
35
35
  Global uniqueness is easily achieved by using a UUID, as specified in RFC 4122, for the mRID. The use of UUID is strongly recommended."""
36
36
 
@@ -46,6 +46,9 @@ class IdentifiedObject(object, metaclass=ABCMeta):
46
46
 
47
47
  def __init__(self, names: Optional[List[Name]] = None, **kwargs):
48
48
  super(IdentifiedObject, self).__init__(**kwargs)
49
+ if not self.mrid or not self.mrid.strip():
50
+ raise ValueError("You must provide an mRID for this object.")
51
+
49
52
  if names:
50
53
  for name in names:
51
54
  self.add_name(name.type, name.name)
@@ -28,7 +28,8 @@ class RegulatingCondEq(EnergyConnection):
28
28
 
29
29
  def __init__(self, regulating_control: Optional[RegulatingControl] = None, **kwargs):
30
30
  super(RegulatingCondEq, self).__init__(**kwargs)
31
- self.regulating_control = regulating_control
31
+ if regulating_control:
32
+ self.regulating_control = regulating_control
32
33
 
33
34
  @property
34
35
  def regulating_control(self):
@@ -30,8 +30,8 @@ class CollectionDifference(Difference):
30
30
 
31
31
  @dataclass()
32
32
  class ObjectDifference(Difference):
33
- source: T
34
- target: T
33
+ source: IdentifiedObject
34
+ target: IdentifiedObject
35
35
  differences: Dict[str, Difference] = field(default_factory=dict)
36
36
 
37
37
 
@@ -79,7 +79,7 @@ def document_to_cim(pb: PBDocument, cim: Document, service: BaseService):
79
79
  @bind_to_cim
80
80
  @add_to_network_or_none
81
81
  def organisation_to_cim(pb: PBOrganisation, service: BaseService) -> Optional[Organisation]:
82
- cim = Organisation()
82
+ cim = Organisation(mrid=pb.mrid())
83
83
 
84
84
  identified_object_to_cim(pb.io, cim, service)
85
85
  return cim
@@ -18,7 +18,7 @@ from zepben.ewb.model.cim.iec61970.base.wires.junction import Junction
18
18
  from zepben.ewb.model.cim.iec61970.base.wires.power_transformer import PowerTransformer
19
19
  from zepben.ewb.model.cim.iec61970.base.wires.power_transformer_end import PowerTransformerEnd
20
20
  from zepben.ewb.services.network.network_service import NetworkService
21
- from zepben.ewb.util import CopyableUUID
21
+ from zepben.ewb.util import generate_id
22
22
 
23
23
 
24
24
  # !! WARNING !! #
@@ -39,7 +39,7 @@ def create_two_winding_power_transformer(network_service: NetworkService, cn1: C
39
39
  _connect_two_terminal_conducting_equipment(network_service=network_service, ce=power_transformer, cn1=cn1, cn2=cn2)
40
40
  # TODO: How to associated PowerTransformerEndInfo to a PowerTransformerInfo
41
41
  for i in range(1, 2):
42
- end = PowerTransformerEnd(power_transformer=power_transformer)
42
+ end = PowerTransformerEnd(f"{power_transformer.mrid}-pte{i}", power_transformer=power_transformer)
43
43
  power_transformer.add_end(end)
44
44
  end.terminal = power_transformer.get_terminal_by_sn(i)
45
45
  return power_transformer
@@ -69,7 +69,7 @@ def create_breaker(network_service: NetworkService, cn1: ConnectivityNode, cn2:
69
69
  def create_bus(network_service: NetworkService, **kwargs) -> Junction:
70
70
  bus = Junction(**kwargs)
71
71
  if 'mrid' not in kwargs:
72
- bus.mrid = str(CopyableUUID())
72
+ bus.mrid = generate_id()
73
73
  network_service.add(bus)
74
74
  _create_terminals(ce=bus, network=network_service)
75
75
  # TODO: Figure out how to add Voltage to Buses - Looks like we need to add topologicalNode to support the
@@ -79,7 +79,7 @@ def create_bus(network_service: NetworkService, **kwargs) -> Junction:
79
79
 
80
80
  def _create_two_terminal_conducting_equipment(network_service: NetworkService, ce: ConductingEquipment, **kwargs):
81
81
  if 'mrid' not in kwargs:
82
- ce.mrid = str(CopyableUUID())
82
+ ce.mrid = generate_id()
83
83
  network_service.add(ce)
84
84
  _create_terminals(ce=ce, num_terms=2, network=network_service)
85
85
 
@@ -92,7 +92,7 @@ def _connect_two_terminal_conducting_equipment(network_service: NetworkService,
92
92
 
93
93
  def _create_single_terminal_conducting_equipment(network_service: NetworkService, ce: ConductingEquipment, **kwargs):
94
94
  if 'mrid' not in kwargs:
95
- ce.mrid = str(CopyableUUID())
95
+ ce.mrid = generate_id()
96
96
  network_service.add(ce)
97
97
  _create_terminals(ce=ce, network=network_service)
98
98
 
zepben/ewb/util.py CHANGED
@@ -15,7 +15,7 @@ __all__ = [
15
15
  "is_none_or_empty",
16
16
  "require",
17
17
  "pb_or_none",
18
- "CopyableUUID",
18
+ "generate_id",
19
19
  "datetime_to_timestamp",
20
20
  "none",
21
21
  "classproperty",
@@ -170,14 +170,8 @@ def none(collection: Collection):
170
170
  raise ValueError("none() only supports collection types")
171
171
 
172
172
 
173
- class CopyableUUID(UUID):
174
-
175
- def __init__(self):
176
- super().__init__(bytes=os.urandom(16), version=4)
177
-
178
- @staticmethod
179
- def copy():
180
- return str(UUID(bytes=os.urandom(16), version=4))
173
+ def generate_id() -> str:
174
+ return str(UUID(bytes=os.urandom(16), version=4))
181
175
 
182
176
 
183
177
  class classproperty(property):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zepben.ewb
3
- Version: 1.0.4b1
3
+ Version: 1.1.0
4
4
  Summary: Python SDK for interacting with the Energy Workbench platform
5
5
  Author-email: Kurt Greaves <kurt.greaves@zepben.com>, Max Chesterfield <max.chesterfield@zepben.com>
6
6
  License-Expression: MPL-2.0
@@ -15,17 +15,18 @@ Requires-Python: <3.13,>=3.10
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
17
  Requires-Dist: zepben.protobuf==1.0.0
18
- Requires-Dist: typing_extensions==4.12.2
19
- Requires-Dist: requests<3.0.0,>=2.26.0
20
- Requires-Dist: urllib3<1.27.0,>=1.26.6
21
- Requires-Dist: PyJWT<2.2.0,>=2.1.0
18
+ Requires-Dist: typing_extensions==4.14.1
19
+ Requires-Dist: requests==2.32.5
20
+ Requires-Dist: urllib3==2.5.0
21
+ Requires-Dist: PyJWT==2.10.1
22
+ Requires-Dist: dataclassy==0.6.2
22
23
  Provides-Extra: test
23
- Requires-Dist: pytest<9,>=7.4.4; extra == "test"
24
- Requires-Dist: pytest-cov==6.1.1; extra == "test"
25
- Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
24
+ Requires-Dist: pytest==8.4.1; extra == "test"
25
+ Requires-Dist: pytest-cov==6.2.1; extra == "test"
26
+ Requires-Dist: pytest-asyncio==1.1.0; extra == "test"
26
27
  Requires-Dist: pytest-timeout==2.4.0; extra == "test"
27
- Requires-Dist: pytest-subtests; extra == "test"
28
- Requires-Dist: hypothesis==6.119.4; extra == "test"
28
+ Requires-Dist: pytest-subtests==0.14.2; extra == "test"
29
+ Requires-Dist: hypothesis==6.140.3; extra == "test"
29
30
  Requires-Dist: grpcio-testing==1.61.3; extra == "test"
30
31
  Requires-Dist: pylint==2.14.5; extra == "test"
31
32
  Requires-Dist: six==1.16.0; extra == "test"
@@ -1,7 +1,7 @@
1
- zepben/ewb/__init__.py,sha256=B5sJdP6ozOagFMdAD8AYfcqAqicoHjug28IiRS9FYp8,40514
1
+ zepben/ewb/__init__.py,sha256=nPkN55KJGBRGRk7fLZAFmzaxu9Z7RR7gGAAwFWdy3lY,40580
2
2
  zepben/ewb/exceptions.py,sha256=KLR6_5U-K4_VtKQZkKBlbOF7wlKnajQuhSiGeeNAo9U,1384
3
3
  zepben/ewb/types.py,sha256=067jjQX6eCbgaEtlQPdSBi_w4_16unbP1f_g5NrVj_w,627
4
- zepben/ewb/util.py,sha256=lgxqbGEQO8df-Bs88-4bVijB8CY9rY8ox8WjibqZWnM,5728
4
+ zepben/ewb/util.py,sha256=JAa6epASTcxbu6gISzYJPjCGhckIugMd6tnVRMlSlMc,5605
5
5
  zepben/ewb/auth/__init__.py,sha256=DUsi8JWvKMQt4xEUCHbCVPjGkEfr2MRu2JIvobYTB-M,406
6
6
  zepben/ewb/auth/client/__init__.py,sha256=nFdcikJb3FegBko35m1xxmjMmC3cZCaqr8ohypQJQIQ,245
7
7
  zepben/ewb/auth/client/zepben_token_fetcher.py,sha256=q1-co2LSWPwuSzzUZmNLYmjsQaCIrfSu4tOxwS_TMMk,12201
@@ -12,7 +12,8 @@ zepben/ewb/auth/common/auth_provider_config.py,sha256=-MkmrOXFgp2K-Q9f-oyaNr2Qhe
12
12
  zepben/ewb/database/__init__.py,sha256=waADXEvfUG9wAN4STx5uIUHOv0UnpZLH2qU1LXgaDBc,243
13
13
  zepben/ewb/database/paths/__init__.py,sha256=waADXEvfUG9wAN4STx5uIUHOv0UnpZLH2qU1LXgaDBc,243
14
14
  zepben/ewb/database/paths/database_type.py,sha256=6l43Q87n3pQ4ClG9zfbp7cRSjt-Fxb01Tdrt48ZHMbk,1025
15
- zepben/ewb/database/paths/ewb_data_file_paths.py,sha256=hoED3sNmnB7tQF9dMPcCH9ozOa5P85u_RewZjqsPMbs,10426
15
+ zepben/ewb/database/paths/ewb_data_file_paths.py,sha256=MchDxklK9atSxOUtq28Yi4hRyilVk2GF8deMrsMdBt4,8063
16
+ zepben/ewb/database/paths/local_ewb_data_file_paths.py,sha256=XeOzoTvuaheD6KAM6EGW56rkiSDHIPE-x7_8iP5vQR0,2390
16
17
  zepben/ewb/database/sql/__init__.py,sha256=8-znO960twGtcAGArLGl_ijbCB9BBv0_hUNYf1eF0Lk,243
17
18
  zepben/ewb/database/sql/column.py,sha256=migPYL0in18cCWd3H51M-op2uqJeYqGv9KIksSiv75s,1049
18
19
  zepben/ewb/database/sql/sql_table.py,sha256=0ADKRb_2uAz0iSz52wbwOjovctvwRte8sHBBfEGj7cU,5235
@@ -22,7 +23,7 @@ zepben/ewb/database/sqlite/common/base_cim_reader.py,sha256=RmJfeKtmvANUcam_EAVM
22
23
  zepben/ewb/database/sqlite/common/base_cim_writer.py,sha256=sPYhkLlpyFt-i0B4UST2EF42i87N0V3Cb7XVaeL-WIU,8021
23
24
  zepben/ewb/database/sqlite/common/base_collection_reader.py,sha256=yuOSWaztkJmlarCRcHtZslzYOg9UeVkNwjtuJLI6XCA,3755
24
25
  zepben/ewb/database/sqlite/common/base_collection_writer.py,sha256=btq5hm57KnL3N4nUCoTLSstamJhFZcK8PM4WsnErm1g,2622
25
- zepben/ewb/database/sqlite/common/base_database_reader.py,sha256=OmHKr1S2463BYAUpomyAKm6hHMVCygDeVVBCuDD3WgA,4924
26
+ zepben/ewb/database/sqlite/common/base_database_reader.py,sha256=q13wG7fZBuxo0UBuDrCPJ2ke0fGTt6Ap5CifjtF5new,5213
26
27
  zepben/ewb/database/sqlite/common/base_database_tables.py,sha256=Bu-eBNs4VQENhdZiQt1o9oB2chXhm6CUrsbSNkcEwBE,5589
27
28
  zepben/ewb/database/sqlite/common/base_database_writer.py,sha256=GStTD4J6F3PlbI1arInqwdazQqngPEaFsItzbzvj3I8,7076
28
29
  zepben/ewb/database/sqlite/common/base_entry_writer.py,sha256=c1u1OjFKw9aB0L6kLTfoHbuKORrYYg83geBOXdUElZY,1278
@@ -366,7 +367,7 @@ zepben/ewb/model/cim/iec61968/infiec61968/infassets/streetlight_lamp_kind.py,sha
366
367
  zepben/ewb/model/cim/iec61968/infiec61968/infcommon/__init__.py,sha256=waADXEvfUG9wAN4STx5uIUHOv0UnpZLH2qU1LXgaDBc,243
367
368
  zepben/ewb/model/cim/iec61968/infiec61968/infcommon/ratio.py,sha256=1AHSbspQH822s7Pwx6oHUpFXgSaNh0rGNi9IVZJMXMc,1190
368
369
  zepben/ewb/model/cim/iec61968/metering/__init__.py,sha256=waADXEvfUG9wAN4STx5uIUHOv0UnpZLH2qU1LXgaDBc,243
369
- zepben/ewb/model/cim/iec61968/metering/controlled_appliance.py,sha256=wvGmpMqoKz6UoE6zzMbW6m3ma5I0ye7qNR_CCIUTdUE,4390
370
+ zepben/ewb/model/cim/iec61968/metering/controlled_appliance.py,sha256=3yU3yuKomb4V5BefJ5x7usVMTQOaggfVlYmL7KjqR-M,4473
370
371
  zepben/ewb/model/cim/iec61968/metering/end_device.py,sha256=UzmPz-thDn5FeLuckKuTWCeMXlTtgltHr9t1BYdOFpw,6594
371
372
  zepben/ewb/model/cim/iec61968/metering/end_device_function.py,sha256=AHZjuTs9TDeVX3SDhoHX2bCE6zwiUTfxVBWvA-k25aA,582
372
373
  zepben/ewb/model/cim/iec61968/metering/end_device_function_kind.py,sha256=c0cze0jp3SgOvEE2qZqL_ye3RLLvR3UIG5mAD8Slf4U,1299
@@ -395,7 +396,7 @@ zepben/ewb/model/cim/iec61970/base/core/equipment.py,sha256=y5qhryMlYmcJfMQYc4wF
395
396
  zepben/ewb/model/cim/iec61970/base/core/equipment_container.py,sha256=1GjHcISjQlh9OqgIJ3Sy5VguQvoJ1Ts_lYCkdkVnLHQ,8116
396
397
  zepben/ewb/model/cim/iec61970/base/core/feeder.py,sha256=5gG8KopIsp1z9D3r9sCUc5y2I7oiTgJ7LF-DRhm2PT4,11368
397
398
  zepben/ewb/model/cim/iec61970/base/core/geographical_region.py,sha256=LsOfVHuWL32zjPvgylpqiTsP9uvOeREVKgvKYMgZOOQ,3852
398
- zepben/ewb/model/cim/iec61970/base/core/identified_object.py,sha256=n6cHj923enWYsbgcAT5IRBvmirc7M5e-MuwaIrvRPzc,9833
399
+ zepben/ewb/model/cim/iec61970/base/core/identified_object.py,sha256=TwY7M6zpd-Wvb8LxyHAUFJq6-lkioUtSVqxh0P689Gc,9928
399
400
  zepben/ewb/model/cim/iec61970/base/core/name.py,sha256=Jgq664dfkYxVgYt4ThJM6g5NaJg40GW5ePZg4nVRC8Q,1285
400
401
  zepben/ewb/model/cim/iec61970/base/core/name_type.py,sha256=N-ZCjV_ihvq4iTVBSrVYuew4Uc9gtFSJq1qElcK9jmI,7888
401
402
  zepben/ewb/model/cim/iec61970/base/core/phase_code.py,sha256=8PWCBOSDniP_921sD8Qh30RVUTdWfWur0NvdeZLXlTI,7049
@@ -477,7 +478,7 @@ zepben/ewb/model/cim/iec61970/base/wires/protected_switch.py,sha256=yMn2MMvbViXL
477
478
  zepben/ewb/model/cim/iec61970/base/wires/ratio_tap_changer.py,sha256=iYrZd8rjFfuPC-wGuYFKfNRBuHuA2Ss9ad_Gnt7qj_o,1161
478
479
  zepben/ewb/model/cim/iec61970/base/wires/reactive_capability_curve.py,sha256=RvnW7HjAxsl3DNM9ABgzBqaoOSnbPBVwdvoIWwCMMj8,824
479
480
  zepben/ewb/model/cim/iec61970/base/wires/recloser.py,sha256=UmL-qht9TL_VDZMUdWQ_4HeCG6dZLl_1G1ggro802VY,541
480
- zepben/ewb/model/cim/iec61970/base/wires/regulating_cond_eq.py,sha256=khnRHTXAsosUvcQsXN7cv3vWzP_OYsLXUeKuvHkG6Ow,1792
481
+ zepben/ewb/model/cim/iec61970/base/wires/regulating_cond_eq.py,sha256=ead_d22h_GieSz2soOfBhaUq_B1q3zie1lKVK9uQs_s,1827
481
482
  zepben/ewb/model/cim/iec61970/base/wires/regulating_control.py,sha256=tyYfQHj_Fo4WJyyOglkvxg5D_D4zeB1kniH3EvcmHsk,9343
482
483
  zepben/ewb/model/cim/iec61970/base/wires/regulating_control_mode_kind.py,sha256=qzxns3WLPo_bICQgctvPuz_667RB2gG0s51i7JXKH98,1203
483
484
  zepben/ewb/model/cim/iec61970/base/wires/rotating_machine.py,sha256=pn8V5O5TPIhmWWX1hri27INs_iOEvQePukvjEN4A8HY,1513
@@ -502,7 +503,7 @@ zepben/ewb/services/services.py,sha256=BQxw0oscF2j-nOHVpyDsTa2h6rLoaGEBBagaBcjZT
502
503
  zepben/ewb/services/common/__init__.py,sha256=yWD_2hLps_qzUFigJx1MY4Fer-UL9zQKtwuHlreYPis,956
503
504
  zepben/ewb/services/common/base_service.py,sha256=xvfIuChJMqD-FOU6iFhxjjt4iKb9Lm3ulwMZ5wksn_4,18547
504
505
  zepben/ewb/services/common/base_service_comparator.py,sha256=uHHBfWTSbJD3yvpb5o6cMMhXK1s4I1ygyRyTdGa-Mp4,18664
505
- zepben/ewb/services/common/difference.py,sha256=CImgrjWPRlpE79ENKoR7mQmiwIYRZWUcPci3YS7ZCSE,1184
506
+ zepben/ewb/services/common/difference.py,sha256=KbMd6PV4_CKSHVvR9evc6EcQYD2gBHd3_bKtYu_FWzQ,1214
506
507
  zepben/ewb/services/common/enum_mapper.py,sha256=B3FJLg5HVOc_wdFbokjKSgPudHWjUzGI6RyihzEoBvU,2323
507
508
  zepben/ewb/services/common/reference_resolvers.py,sha256=M8QDJWtcOdrcl4-pXOg5dY9xGSFPM4KnqwKT-D9_-fM,26914
508
509
  zepben/ewb/services/common/resolver.py,sha256=SaH5azHzq1cQsikbaf3i_ODA73rHyELyEm1s3Mu-tyw,26165
@@ -513,7 +514,7 @@ zepben/ewb/services/common/meta/metadata_translations.py,sha256=O8tW3YVrogmKEAOE
513
514
  zepben/ewb/services/common/meta/service_info.py,sha256=EX1iwDv6ENS4la-hs9XQXjfKv10bM-P_WYx48xW42ik,609
514
515
  zepben/ewb/services/common/translator/__init__.py,sha256=waADXEvfUG9wAN4STx5uIUHOv0UnpZLH2qU1LXgaDBc,243
515
516
  zepben/ewb/services/common/translator/base_cim2proto.py,sha256=OxlXiFiZGxdinqBgbmHSOEnOZNKhJgmvg_jswDSgKkw,3860
516
- zepben/ewb/services/common/translator/base_proto2cim.py,sha256=wRdnwFD0GeWy_rrJ3v76dGYevkJ78UeMNbTGXRhFE9g,5203
517
+ zepben/ewb/services/common/translator/base_proto2cim.py,sha256=afGTsUnax3fvZTfAm014OM5dZiIlZwxLJxxGxgU02_Q,5217
517
518
  zepben/ewb/services/common/translator/service_differences.py,sha256=ZFbGFMxTTnT4zt4zIt2BK_ikg_71vGkkmH8JhII2bMc,2913
518
519
  zepben/ewb/services/common/translator/util.py,sha256=C7KGB5B7I3j2uSlDLtKUeQUNSFqLCqz4u9vA-TKKE34,2292
519
520
  zepben/ewb/services/customer/__init__.py,sha256=waADXEvfUG9wAN4STx5uIUHOv0UnpZLH2qU1LXgaDBc,243
@@ -536,7 +537,7 @@ zepben/ewb/services/measurement/translator/__init__.py,sha256=IoQHJ-CYDhqiYL6WKK
536
537
  zepben/ewb/services/measurement/translator/measurement_cim2proto.py,sha256=syPJC3FtXyNrJNRDc4oCLZUL0kzgyXRI7CsRmrbznhI,2114
537
538
  zepben/ewb/services/measurement/translator/measurement_proto2cim.py,sha256=fsRXt_txPzUWDrFreZ1C1LcizUZQ-qlFq2aGADo9Q_E,2326
538
539
  zepben/ewb/services/network/__init__.py,sha256=waADXEvfUG9wAN4STx5uIUHOv0UnpZLH2qU1LXgaDBc,243
539
- zepben/ewb/services/network/network_extensions.py,sha256=8vbY0wkgTx-CosTzuENc2iGjJQaCxb6xEgjrDqLkZ5c,6110
540
+ zepben/ewb/services/network/network_extensions.py,sha256=PGkeW2z5WNpQ8MeQIfCpTUL7ukne2uHO9R-PlBKEWyg,6127
540
541
  zepben/ewb/services/network/network_service.py,sha256=tfmafoAK36bv_aMPL0zCagd3-RjUwGJX1MUeF5xZup4,12574
541
542
  zepben/ewb/services/network/network_service_comparator.py,sha256=sxbseccB4l_K399rJVJxnmaOT6m4QGKlNbdTtBGbBfs,61978
542
543
  zepben/ewb/services/network/network_state.py,sha256=PQo1xm6p4WGHzLtd4zy3nYMJ6miR3ypj7hSQx_dRgXY,837
@@ -634,8 +635,8 @@ zepben/ewb/streaming/mutations/update_network_state_client.py,sha256=e0Oma5PRT8m
634
635
  zepben/ewb/streaming/mutations/update_network_state_service.py,sha256=irR-TO67QXRyBmK8PU8SzM31NKSSefZt_nQGHi5IhT8,3260
635
636
  zepben/ewb/testing/__init__.py,sha256=waADXEvfUG9wAN4STx5uIUHOv0UnpZLH2qU1LXgaDBc,243
636
637
  zepben/ewb/testing/test_network_builder.py,sha256=KG0o2ZHUswx3xClu-JnLs_pYIYbQ5jjtvtyZ7LI6IZ8,38092
637
- zepben_ewb-1.0.4b1.dist-info/licenses/LICENSE,sha256=aAHD66h6PQIETpkJDvg5yEObyFvXUED8u7S8dlh6K0Y,16725
638
- zepben_ewb-1.0.4b1.dist-info/METADATA,sha256=JomV9pD_lfj-hhAhlDXOdprXUyNiycCjnaXOA349HKk,3217
639
- zepben_ewb-1.0.4b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
640
- zepben_ewb-1.0.4b1.dist-info/top_level.txt,sha256=eVLDJiO6FGjL_Z7KdmFE-R8uf1Q07aaVLGe9Ee4kmBw,7
641
- zepben_ewb-1.0.4b1.dist-info/RECORD,,
638
+ zepben_ewb-1.1.0.dist-info/licenses/LICENSE,sha256=aAHD66h6PQIETpkJDvg5yEObyFvXUED8u7S8dlh6K0Y,16725
639
+ zepben_ewb-1.1.0.dist-info/METADATA,sha256=74QEghgDPCso_MX_O1zEJDXba1F6v8VlvXop9DkWe68,3230
640
+ zepben_ewb-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
641
+ zepben_ewb-1.1.0.dist-info/top_level.txt,sha256=eVLDJiO6FGjL_Z7KdmFE-R8uf1Q07aaVLGe9Ee4kmBw,7
642
+ zepben_ewb-1.1.0.dist-info/RECORD,,