commonlib-reader 1.3.0__tar.gz → 1.4.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: commonlib-reader
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: Reader for Equinor commonlib api.
5
5
  Author: Åsmund Våge Fannemel
6
6
  Author-email: Åsmund Våge Fannemel <asmf@equinor.com>
@@ -14,9 +14,12 @@ Description-Content-Type: text/markdown
14
14
  # commonlib-reader
15
15
  Connector package for Equinor [Commonlib](https://commonlib.equinor.com/) [api](https://commonlibapi.equinor.com/swagger/index.html).
16
16
 
17
+ See the [changelog](CHANGELOG.md) for release history.
18
+
17
19
  Current features:
18
20
  - Reading any code table with the `Code` class
19
21
  - Reading library definitions and attribute definitions with the `Library` and `AttributeDefinition` classes
22
+ - Reading projects with the `Project` class
20
23
  - Getting facility data using the [Facility](commonlib_reader/facility.py) class
21
24
  - [IMS source ](commonlib_reader/ims.py) lookup tables for facilities
22
25
  - Getting Tag category, Tag type, Tag format, and Tag format element data. See [tag.py](commonlib_reader/tag.py)
@@ -71,6 +74,18 @@ Each entry provides `name`, `description`, `identity`, `is_valid`, `attributes`,
71
74
  other CommonLib metadata. Use `Code.get_names()` or `Code.get_name_and_desc()` when
72
75
  only dropdown-friendly names or name/description data is required.
73
76
 
77
+ ### Projects
78
+
79
+ Use `Project.get_all()` to retrieve projects and optionally filter by name or
80
+ abbreviation.
81
+
82
+ ```python
83
+ from commonlib_reader import Project
84
+
85
+ projects = Project.get_all(name="M.IGLED.6I.X.0010")
86
+ matching_projects = Project.get_all(abbreviation="IGLED")
87
+ ```
88
+
74
89
  ## Installing
75
90
 
76
91
  Install package from pypi using `pip install commonlib_reader`
@@ -1,9 +1,12 @@
1
1
  # commonlib-reader
2
2
  Connector package for Equinor [Commonlib](https://commonlib.equinor.com/) [api](https://commonlibapi.equinor.com/swagger/index.html).
3
3
 
4
+ See the [changelog](CHANGELOG.md) for release history.
5
+
4
6
  Current features:
5
7
  - Reading any code table with the `Code` class
6
8
  - Reading library definitions and attribute definitions with the `Library` and `AttributeDefinition` classes
9
+ - Reading projects with the `Project` class
7
10
  - Getting facility data using the [Facility](commonlib_reader/facility.py) class
8
11
  - [IMS source ](commonlib_reader/ims.py) lookup tables for facilities
9
12
  - Getting Tag category, Tag type, Tag format, and Tag format element data. See [tag.py](commonlib_reader/tag.py)
@@ -58,6 +61,18 @@ Each entry provides `name`, `description`, `identity`, `is_valid`, `attributes`,
58
61
  other CommonLib metadata. Use `Code.get_names()` or `Code.get_name_and_desc()` when
59
62
  only dropdown-friendly names or name/description data is required.
60
63
 
64
+ ### Projects
65
+
66
+ Use `Project.get_all()` to retrieve projects and optionally filter by name or
67
+ abbreviation.
68
+
69
+ ```python
70
+ from commonlib_reader import Project
71
+
72
+ projects = Project.get_all(name="M.IGLED.6I.X.0010")
73
+ matching_projects = Project.get_all(abbreviation="IGLED")
74
+ ```
75
+
61
76
  ## Installing
62
77
 
63
78
  Install package from pypi using `pip install commonlib_reader`
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "commonlib-reader"
3
- version = "1.3.0"
3
+ version = "1.4.0"
4
4
  description = "Reader for Equinor commonlib api."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10,<4.0.0"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "commonlib-reader"
3
- version = "1.3.0"
3
+ version = "1.4.0"
4
4
  description = "Reader for Equinor commonlib api."
5
5
  authors = [
6
6
  { name = "Åsmund Våge Fannemel", email = "asmf@equinor.com" }
@@ -8,9 +8,8 @@ class Code:
8
8
  """A generic representation of one entry in a CommonLib library.
9
9
 
10
10
  A Library defines the code table and its attribute schema; a Code stores one
11
- value from that table. Use this class to read entries from libraries without
12
- a dedicated domain class implemented in this package, such as Facility,
13
- Discipline, Unit, TagType, TagCategory, or TagFormat.
11
+ value from that table. Use this class directly for generic libraries, or as
12
+ a base class for dedicated readers that add library-specific behavior.
14
13
 
15
14
  Attributes:
16
15
  library (str): Name of the library the code belongs to.
@@ -3,38 +3,15 @@ from typing import List
3
3
  from commonlib_reader.code import Code
4
4
 
5
5
 
6
- class Discipline:
6
+ class Discipline(Code):
7
7
  def __init__(self, data: dict):
8
8
  if not isinstance(data, dict):
9
9
  raise ValueError("Input data shall be dictionary.")
10
-
11
- self._data = data
12
-
13
- def __eq__(self, other):
14
- if isinstance(other, Discipline):
15
- return self._data == other._data
16
-
17
- return False
10
+ super().__init__(data, library="Discipline")
18
11
 
19
12
  def __str__(self):
20
13
  return f"Discipline: {self.name}-{self.description}"
21
14
 
22
- @property
23
- def name(self) -> str:
24
- return self._data.get("name", "")
25
-
26
- @property
27
- def description(self) -> str:
28
- return self._data.get("description", "")
29
-
30
- @property
31
- def identity(self) -> str:
32
- return self._data.get("identity", "")
33
-
34
- @property
35
- def is_valid(self) -> bool:
36
- return self._data.get("isValid", False)
37
-
38
15
  @classmethod
39
16
  def get_all(cls, scope: str, only_valid: bool = True) -> List["Discipline"]:
40
17
  """Get all disciplines for
@@ -57,7 +34,7 @@ class Discipline:
57
34
  f"No discipline data found for scope {scope}. Verify that scope is correct."
58
35
  )
59
36
 
60
- dis = [Discipline(x) for x in dis_data] # type: ignore
37
+ dis = [cls(x) for x in dis_data]
61
38
  if only_valid:
62
39
  dis = [x for x in dis if x.is_valid]
63
40
 
@@ -0,0 +1,285 @@
1
+ from typing import List, Union
2
+
3
+ from commonlib_reader.code import Code
4
+
5
+
6
+ class Unit(Code):
7
+ """A class representing a unit of measurement.
8
+
9
+ Properties:
10
+ name (str): The name of the unit.
11
+ description (str): A description of the unit.
12
+ identity (str): The unique identifier for the unit.
13
+ quantity (str): The quantity this unit measures.
14
+ """
15
+
16
+ # In memory cache for units and conversion codes
17
+ _units = None
18
+ _conversion_codes = None
19
+
20
+ def __init__(self, data: Union[dict, str]):
21
+ if isinstance(data, str):
22
+ matches = [unit for unit in self.get_units() if unit.name == data]
23
+ if not matches:
24
+ raise ValueError(f"No unit named {data} found.")
25
+ data = matches[0]._data
26
+
27
+ super().__init__(data, library="UnitOfMeasure")
28
+ self.__dict__.update(self.attributes)
29
+
30
+ @property
31
+ def name(self) -> str:
32
+ """Get or set the unit symbol."""
33
+ return self._data.get("name", "")
34
+
35
+ @name.setter
36
+ def name(self, value: str):
37
+ self._data["name"] = value
38
+
39
+ @property
40
+ def description(self) -> str:
41
+ """Get or set the unit description."""
42
+ return self._data.get("description", "")
43
+
44
+ @description.setter
45
+ def description(self, value: str):
46
+ self._data["description"] = value
47
+
48
+ @property
49
+ def identity(self) -> str:
50
+ """Get or set the unit identity."""
51
+ return self._data.get("identity", "")
52
+
53
+ @identity.setter
54
+ def identity(self, value: str):
55
+ self._data["identity"] = value
56
+
57
+ @staticmethod
58
+ def _as_bool(value) -> bool:
59
+ if isinstance(value, bool):
60
+ return value
61
+ return str(value).strip().lower() == "true"
62
+
63
+ @property
64
+ def quantity(self) -> str:
65
+ """Get the quantity measured by this unit."""
66
+ return self.attributes.get("Quantity", "")
67
+
68
+ @property
69
+ def conversion(self) -> str:
70
+ """Get the unit's conversion reference."""
71
+ return self.attributes.get("Conversion", "")
72
+
73
+ @property
74
+ def measure_system(self) -> str:
75
+ """Get the measurement system this unit belongs to."""
76
+ return self.attributes.get("MeasureSystem", "")
77
+
78
+ @property
79
+ def alias_of_uom(self) -> str:
80
+ """Get the unit symbol this unit aliases, if any."""
81
+ return self.attributes.get("AliasOfUoM", "")
82
+
83
+ @property
84
+ def alternate_symbols(self) -> str:
85
+ """Get alternate symbols for this unit."""
86
+ return self.attributes.get("AlternateSymbols", "")
87
+
88
+ @property
89
+ def is_in_base_units(self) -> bool:
90
+ """Whether the unit is expressed using the seven base units."""
91
+ return self._as_bool(self.attributes.get("IsInBaseUnits", False))
92
+
93
+ @property
94
+ def is_to_be_translated_to_alias(self) -> bool:
95
+ """Whether incoming values should be translated to the alias symbol."""
96
+ return self._as_bool(
97
+ self.attributes.get("IsToBeTranslatedToAlias", False)
98
+ )
99
+
100
+ @property
101
+ def is_default_for_transport(self) -> bool:
102
+ """Whether this is the default unit for transport."""
103
+ return self._as_bool(self.attributes.get("IsDefaultForTransport", False))
104
+
105
+ @property
106
+ def is_default_for_print(self) -> bool:
107
+ """Whether this is the default unit for print."""
108
+ return self._as_bool(self.attributes.get("IsDefaultForPrint", False))
109
+
110
+ @property
111
+ def definition(self) -> str:
112
+ """Get the unit definition, if provided."""
113
+ return self.attributes.get("Definition", "")
114
+
115
+ @property
116
+ def remark(self) -> str:
117
+ """Get general notes about the unit."""
118
+ return self.attributes.get("Remark", "")
119
+
120
+ @property
121
+ def iec61360_code(self) -> str:
122
+ """Get the IEC 61360 identifier, if provided."""
123
+ return self.attributes.get("IEC61360Code", "")
124
+
125
+ @property
126
+ def dexpi_name(self) -> str:
127
+ """Get the DEXPI unit name, if provided."""
128
+ return self.attributes.get("DEXPIName", "")
129
+
130
+ @property
131
+ def iso15926_name(self) -> str:
132
+ """Get the ISO 15926 unit name, if provided."""
133
+ return self.attributes.get("ISO15926Name", "")
134
+
135
+ @property
136
+ def iso15926_url(self) -> str:
137
+ """Get the ISO 15926 reference URL, if provided."""
138
+ return self.attributes.get("ISO15926URL", "")
139
+
140
+ @property
141
+ def is_cfihos(self) -> bool:
142
+ """Whether this unit is used by CFIHOS."""
143
+ return self._as_bool(self.attributes.get("IsCFIHOS", False))
144
+
145
+ @property
146
+ def internal_comments(self) -> str:
147
+ """Get internal maintenance comments, if provided."""
148
+ return self.attributes.get("InternalComments", "")
149
+
150
+ @classmethod
151
+ def _get_conversion_codes(cls):
152
+ if cls._conversion_codes is None:
153
+ codes = Code.get_codes("UnitOfMeasureConversion")
154
+ cls._conversion_codes = {code.identity: code for code in codes}
155
+ return cls._conversion_codes
156
+
157
+ def _get_conversion_factors(self):
158
+ """Return the API's ``(FactorA, FactorB)`` conversion coefficients.
159
+
160
+ ``FactorA`` is the scale factor and ``FactorB`` is the offset. The API
161
+ converts a value to its base unit as ``(value - FactorB) / FactorA``;
162
+ the inverse is ``base_value * FactorA + FactorB``.
163
+ """
164
+ reference = self.conversion
165
+ conversion_code = self._get_conversion_codes().get(reference)
166
+ if conversion_code is None:
167
+ raise ValueError(
168
+ f"No conversion factors found for unit {self.name} ({reference})."
169
+ )
170
+
171
+ try:
172
+ factor_a = float(conversion_code.attributes["FactorA"])
173
+ factor_b = float(conversion_code.attributes["FactorB"])
174
+ except (KeyError, TypeError, ValueError):
175
+ raise ValueError(
176
+ f"Invalid conversion factors for unit {self.name} ({reference})."
177
+ ) from None
178
+ return factor_a, factor_b
179
+
180
+ @property
181
+ def factor_a(self) -> float:
182
+ """Get the unit's multiplicative conversion factor."""
183
+ return self._get_conversion_factors()[0]
184
+
185
+ @property
186
+ def factor_b(self) -> float:
187
+ """Get the unit's additive conversion factor."""
188
+ return self._get_conversion_factors()[1]
189
+
190
+ def convert(self, value: float, to_unit: Union[str, "Unit"]) -> float:
191
+ """Convert a value from this unit to another unit of the same quantity.
192
+
193
+ A string target may be either a unit identity (for example,
194
+ ``"Pa|pressure"``) or an unambiguous symbol for this quantity.
195
+ """
196
+ if isinstance(to_unit, str):
197
+ matches = [unit for unit in self.get_units() if unit.identity == to_unit]
198
+ if not matches:
199
+ symbol_matches = [
200
+ unit for unit in self.get_units() if unit.name == to_unit
201
+ ]
202
+ quantity_matches = [
203
+ unit
204
+ for unit in symbol_matches
205
+ if unit.quantity == self.quantity
206
+ ]
207
+ matches = quantity_matches or symbol_matches
208
+ if not matches:
209
+ raise ValueError(f"No target unit {to_unit} found for {self.name}.")
210
+ if len(matches) > 1:
211
+ raise ValueError(f"Target unit {to_unit} is ambiguous.")
212
+ to_unit = matches[0]
213
+ elif not isinstance(to_unit, Unit):
214
+ raise ValueError("Target unit must be a unit symbol, identity, or Unit.")
215
+
216
+ if self.quantity != to_unit.quantity:
217
+ raise ValueError(
218
+ f"Cannot convert between {self.quantity} and {to_unit.quantity}."
219
+ )
220
+
221
+ source_a, source_b = self._get_conversion_factors()
222
+ target_a, target_b = to_unit._get_conversion_factors()
223
+ if source_a == 0:
224
+ raise ValueError(f"Conversion factor A is zero for unit {self.name}.")
225
+
226
+ base_value = (float(value) - source_b) / source_a
227
+ return base_value * target_a + target_b
228
+
229
+ @classmethod
230
+ def get_convertible_units(cls, unit: Union[str, "Unit"]) -> List["Unit"]:
231
+ """List units with usable conversion factors for the same quantity.
232
+
233
+ Args:
234
+ unit: A Unit instance, unit identity, or unambiguous unit symbol.
235
+
236
+ Returns:
237
+ List[Unit]: Compatible units, including the input unit when valid.
238
+
239
+ Raises:
240
+ ValueError: If the input cannot be resolved or has no usable factors.
241
+ """
242
+ if isinstance(unit, str):
243
+ matches = [candidate for candidate in cls.get_units() if candidate.identity == unit]
244
+ if not matches:
245
+ matches = [candidate for candidate in cls.get_units() if candidate.name == unit]
246
+ if not matches:
247
+ raise ValueError(f"No unit named or identified by {unit} found.")
248
+ if len(matches) > 1:
249
+ raise ValueError(f"Unit symbol {unit} is ambiguous; use its identity.")
250
+ unit = matches[0]
251
+ elif not isinstance(unit, cls):
252
+ raise ValueError("Input must be a Unit instance, unit symbol, or identity.")
253
+
254
+ source_a, _ = unit._get_conversion_factors()
255
+ if source_a == 0:
256
+ raise ValueError(f"Conversion factor A is zero for unit {unit.name}.")
257
+
258
+ convertible_units = []
259
+ for candidate in cls.get_units():
260
+ if candidate.quantity != unit.quantity:
261
+ continue
262
+ try:
263
+ factor_a, _ = candidate._get_conversion_factors()
264
+ except ValueError:
265
+ continue
266
+ if factor_a != 0:
267
+ convertible_units.append(candidate)
268
+
269
+ return convertible_units
270
+
271
+ def convertible_units(self) -> List["Unit"]:
272
+ """List units convertible with this unit's quantity."""
273
+ return type(self).get_convertible_units(self)
274
+
275
+ @classmethod
276
+ def get_units(cls) -> List["Unit"]:
277
+ """Get list of Unit objects of entries in code library. Caches locally in memory.
278
+
279
+ Returns:
280
+ List[Unit]: List of Unit objects from entries in code library.
281
+ """
282
+ if cls._units is None:
283
+ cls._units = Code._get_code_data("UnitOfMeasure")
284
+
285
+ return [cls(x) for x in cls._units]
@@ -1,82 +0,0 @@
1
- from typing import List, Union
2
-
3
- from commonlib_reader.code import Code
4
-
5
-
6
- class Unit:
7
- """A class representing a unit of measurement.
8
-
9
- Properties:
10
- name (str): The name of the unit.
11
- description (str): A description of the unit.
12
- identity (str): The unique identifier for the unit.
13
- quantity (str): The quantity this unit measures.
14
- """
15
-
16
- _units = None
17
-
18
- def __init__(self, data: Union[dict, str]):
19
- if isinstance(data, str):
20
- s = [x for x in Unit.get_units() if x.name == data]
21
- if len(s) > 0:
22
- data = s[0]._data
23
- else:
24
- raise ValueError(f"No unit named {data} found.")
25
-
26
- if isinstance(data, dict):
27
- self._data = data
28
- for key in data.keys():
29
- if key == "attributes":
30
- for attr in data["attributes"]:
31
- self.__setattr__(attr["definitionName"], attr["displayValue"])
32
- else:
33
- self.__setattr__(key, data[key])
34
- else:
35
- raise ValueError("Input data must be a dict from codetable")
36
-
37
- @property
38
- def name(self) -> str:
39
- """Get the unit name."""
40
- return getattr(self, "_name", "")
41
-
42
- @name.setter
43
- def name(self, value: str):
44
- """Set the unit name."""
45
- self._name = value
46
-
47
- @property
48
- def description(self) -> str:
49
- """Get the unit description."""
50
- return getattr(self, "_description", "")
51
-
52
- @description.setter
53
- def description(self, value: str):
54
- """Set the unit description."""
55
- self._description = value
56
-
57
- @property
58
- def identity(self) -> str:
59
- """Get the unit identity."""
60
- return getattr(self, "_identity", "")
61
-
62
- @identity.setter
63
- def identity(self, value: str):
64
- """Set the unit identity."""
65
- self._identity = value
66
-
67
- @property
68
- def quantity(self) -> str:
69
- """Get the quantity measured by this unit."""
70
- return getattr(self, "Quantity", "")
71
-
72
- @classmethod
73
- def get_units(cls) -> List["Unit"]:
74
- """Get list of Unit objects of entries in code library. Caches locally in memory.
75
-
76
- Returns:
77
- List[Unit]: List of Unit objects from entries in code library.
78
- """
79
- if cls._units is None:
80
- cls._units = Code._get_code_data("UnitOfMeasure")
81
-
82
- return [Unit(x) for x in cls._units]