commonlib-reader 1.2.1__tar.gz → 1.3.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.
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.3
2
+ Name: commonlib-reader
3
+ Version: 1.3.0
4
+ Summary: Reader for Equinor commonlib api.
5
+ Author: Åsmund Våge Fannemel
6
+ Author-email: Åsmund Våge Fannemel <asmf@equinor.com>
7
+ License: MIT
8
+ Requires-Dist: pandas>=2.1.4
9
+ Requires-Dist: eq-api-connector>=1.1.0,<2.0.0
10
+ Requires-Python: >=3.10, <4.0.0
11
+ Project-URL: Repository, https://github.com/equinor/commonlib-reader.git
12
+ Description-Content-Type: text/markdown
13
+
14
+ # commonlib-reader
15
+ Connector package for Equinor [Commonlib](https://commonlib.equinor.com/) [api](https://commonlibapi.equinor.com/swagger/index.html).
16
+
17
+ Current features:
18
+ - Reading any code table with the `Code` class
19
+ - Reading library definitions and attribute definitions with the `Library` and `AttributeDefinition` classes
20
+ - Getting facility data using the [Facility](commonlib_reader/facility.py) class
21
+ - [IMS source ](commonlib_reader/ims.py) lookup tables for facilities
22
+ - Getting Tag category, Tag type, Tag format, and Tag format element data. See [tag.py](commonlib_reader/tag.py)
23
+ - Getting [units of measure](commonlib_reader/ims.py) definitions.
24
+
25
+
26
+ ## Use
27
+ Try it out by running the [demo](examples/demo.py).
28
+
29
+ ### Libraries and codes
30
+
31
+ A `Library` defines a code table and its attribute schema; a `Code` is one entry
32
+ in that table. Use package-provided specialized readers when available, such as
33
+ `Facility`, `Discipline`, `Unit`, `TagType`, `TagCategory`, and `TagFormat`;
34
+ otherwise use the generic `Code` reader.
35
+
36
+ ### Libraries
37
+
38
+ Use `Library` to discover available code tables and inspect their definitions.
39
+
40
+ ```python
41
+ from commonlib_reader import Library
42
+
43
+ library_names = Library.get_names()
44
+ discipline_library = Library.get("Discipline")
45
+ attribute_definitions = discipline_library.attribute_definitions
46
+ is_scoped = discipline_library.is_scope_specific
47
+ scope_type = discipline_library.scope_type
48
+ ```
49
+
50
+ `Library.get_all()` supports name and scope filters. Each `AttributeDefinition`
51
+ provides its name, description, required status, identity participation, validation
52
+ regular expression, and referenced library name.
53
+
54
+ Check `is_scope_specific` and `scope_type` before retrieving codes. For example,
55
+ when `scope_type` is `"Facility"` and `is_scope_specific` is `True`, pass the
56
+ facility installation code, such as `"TROC"`, as `scope`.
57
+
58
+ ### Code tables
59
+
60
+ Use `Code.get_codes()` to retrieve entries from any CommonLib code table. Filter by
61
+ installation scope or code name when needed.
62
+
63
+ ```python
64
+ from commonlib_reader import Code
65
+
66
+ disciplines = Code.get_codes("Discipline", scope="TROC")
67
+ administration = Code.get_codes("Discipline", scope="TROC", name="A")
68
+ ```
69
+
70
+ Each entry provides `name`, `description`, `identity`, `is_valid`, `attributes`, and
71
+ other CommonLib metadata. Use `Code.get_names()` or `Code.get_name_and_desc()` when
72
+ only dropdown-friendly names or name/description data is required.
73
+
74
+ ## Installing
75
+
76
+ Install package from pypi using `pip install commonlib_reader`
77
+
78
+
79
+ ## Developing / testing
80
+
81
+ uv is preferred for developers. Clone and install with required packages for testing and coverage:
82
+ `uv sync`
83
+
84
+ For testing with coverage run:
85
+ `uv run pytest --cov --cov-report=html`
@@ -0,0 +1,72 @@
1
+ # commonlib-reader
2
+ Connector package for Equinor [Commonlib](https://commonlib.equinor.com/) [api](https://commonlibapi.equinor.com/swagger/index.html).
3
+
4
+ Current features:
5
+ - Reading any code table with the `Code` class
6
+ - Reading library definitions and attribute definitions with the `Library` and `AttributeDefinition` classes
7
+ - Getting facility data using the [Facility](commonlib_reader/facility.py) class
8
+ - [IMS source ](commonlib_reader/ims.py) lookup tables for facilities
9
+ - Getting Tag category, Tag type, Tag format, and Tag format element data. See [tag.py](commonlib_reader/tag.py)
10
+ - Getting [units of measure](commonlib_reader/ims.py) definitions.
11
+
12
+
13
+ ## Use
14
+ Try it out by running the [demo](examples/demo.py).
15
+
16
+ ### Libraries and codes
17
+
18
+ A `Library` defines a code table and its attribute schema; a `Code` is one entry
19
+ in that table. Use package-provided specialized readers when available, such as
20
+ `Facility`, `Discipline`, `Unit`, `TagType`, `TagCategory`, and `TagFormat`;
21
+ otherwise use the generic `Code` reader.
22
+
23
+ ### Libraries
24
+
25
+ Use `Library` to discover available code tables and inspect their definitions.
26
+
27
+ ```python
28
+ from commonlib_reader import Library
29
+
30
+ library_names = Library.get_names()
31
+ discipline_library = Library.get("Discipline")
32
+ attribute_definitions = discipline_library.attribute_definitions
33
+ is_scoped = discipline_library.is_scope_specific
34
+ scope_type = discipline_library.scope_type
35
+ ```
36
+
37
+ `Library.get_all()` supports name and scope filters. Each `AttributeDefinition`
38
+ provides its name, description, required status, identity participation, validation
39
+ regular expression, and referenced library name.
40
+
41
+ Check `is_scope_specific` and `scope_type` before retrieving codes. For example,
42
+ when `scope_type` is `"Facility"` and `is_scope_specific` is `True`, pass the
43
+ facility installation code, such as `"TROC"`, as `scope`.
44
+
45
+ ### Code tables
46
+
47
+ Use `Code.get_codes()` to retrieve entries from any CommonLib code table. Filter by
48
+ installation scope or code name when needed.
49
+
50
+ ```python
51
+ from commonlib_reader import Code
52
+
53
+ disciplines = Code.get_codes("Discipline", scope="TROC")
54
+ administration = Code.get_codes("Discipline", scope="TROC", name="A")
55
+ ```
56
+
57
+ Each entry provides `name`, `description`, `identity`, `is_valid`, `attributes`, and
58
+ other CommonLib metadata. Use `Code.get_names()` or `Code.get_name_and_desc()` when
59
+ only dropdown-friendly names or name/description data is required.
60
+
61
+ ## Installing
62
+
63
+ Install package from pypi using `pip install commonlib_reader`
64
+
65
+
66
+ ## Developing / testing
67
+
68
+ uv is preferred for developers. Clone and install with required packages for testing and coverage:
69
+ `uv sync`
70
+
71
+ For testing with coverage run:
72
+ `uv run pytest --cov --cov-report=html`
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "commonlib-reader"
3
+ version = "1.3.0"
4
+ description = "Reader for Equinor commonlib api."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10,<4.0.0"
7
+ dependencies = [
8
+ "pandas>=2.1.4",
9
+ "eq-api-connector (>=1.1.0,<2.0.0)",
10
+ ]
11
+
12
+ [[project.authors]]
13
+ name = "Åsmund Våge Fannemel"
14
+ email = "asmf@equinor.com"
15
+
16
+ [project.license]
17
+ text = "MIT"
18
+
19
+ [project.urls]
20
+ Repository = "https://github.com/equinor/commonlib-reader.git"
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "pytest>=7.4.4,<10.0.0",
25
+ "pytest-cov>=7.0.0,<8.0.0",
26
+ "black>=24.1.1,<27.0.0",
27
+ ]
28
+
29
+ [build-system]
30
+ requires = ["uv_build>=0.11.16,<0.13"]
31
+ build-backend = "uv_build"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "commonlib-reader"
3
- version = "1.2.1"
3
+ version = "1.3.0"
4
4
  description = "Reader for Equinor commonlib api."
5
5
  authors = [
6
6
  { name = "Åsmund Våge Fannemel", email = "asmf@equinor.com" }
@@ -22,5 +22,5 @@ dev = [
22
22
  ]
23
23
 
24
24
  [build-system]
25
- requires = ["uv_build>=0.11.16,<0.12"]
25
+ requires = ["uv_build>=0.11.16,<0.13"]
26
26
  build-backend = "uv_build"
@@ -1,25 +1,22 @@
1
- from commonlib_reader.utils import (
2
- get_code,
3
- get_code_param,
4
- get_library_names,
5
- )
6
-
7
-
8
1
  from .discipline import Discipline
9
2
  from .tag import TagType, TagCategory, TagFormat
10
3
  from .ims import IMS
11
4
  from .facility import Facility
12
5
  from .unit import Unit
6
+ from .code import Code
7
+ from .library import Library, AttributeDefinition
8
+ from .project import Project
13
9
 
14
10
  __all__ = [
11
+ "AttributeDefinition",
12
+ "Code",
15
13
  "Discipline",
16
14
  "Facility",
17
15
  "IMS",
16
+ "Library",
17
+ "Project",
18
18
  "TagType",
19
19
  "TagCategory",
20
20
  "TagFormat",
21
21
  "Unit",
22
- "get_code",
23
- "get_code_param",
24
- "get_library_names",
25
22
  ]
@@ -0,0 +1,170 @@
1
+ from typing import Dict, List, Optional
2
+
3
+ from commonlib_reader.connector import get_connector
4
+ from commonlib_reader.utils import attributes_list_to_dict
5
+
6
+
7
+ class Code:
8
+ """A generic representation of one entry in a CommonLib library.
9
+
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.
14
+
15
+ Attributes:
16
+ library (str): Name of the library the code belongs to.
17
+ name (str): The code name (the code value).
18
+ description (str): Description of the code.
19
+ is_valid (bool): Whether the code can still be used with new data.
20
+ identity (str): Unique identity used to cross reference the code.
21
+ iri (str): CommonLib IRI of the code.
22
+ project (str): Project the code belongs to, if any.
23
+ attachment_key (str): Key used to download an attached file, if any.
24
+ attributes (Dict[str, str]): Library specific attributes keyed by definition name.
25
+ """
26
+
27
+ def __init__(self, data: dict, library: str = ""):
28
+ if not isinstance(data, dict):
29
+ raise ValueError("Input data must be a dict from codetable")
30
+ self._data = data
31
+ self._library = library
32
+ self._attributes = attributes_list_to_dict(self._data.get("attributes", []))
33
+
34
+ def __eq__(self, other):
35
+ if isinstance(other, Code):
36
+ return self._data == other._data and self._library == other._library
37
+ return False
38
+
39
+ def __str__(self):
40
+ return f"{self.name} - {self.description}"
41
+
42
+ @property
43
+ def library(self) -> str:
44
+ return self._library
45
+
46
+ @property
47
+ def name(self) -> str:
48
+ return self._data.get("name", "")
49
+
50
+ @property
51
+ def description(self) -> str:
52
+ return self._data.get("description", "")
53
+
54
+ @property
55
+ def is_valid(self) -> bool:
56
+ return self._data.get("isValid", False)
57
+
58
+ @property
59
+ def identity(self) -> str:
60
+ return self._data.get("identity", "")
61
+
62
+ @property
63
+ def iri(self) -> str:
64
+ return self._data.get("iri", "") or ""
65
+
66
+ @property
67
+ def project(self) -> str:
68
+ return self._data.get("project", "") or ""
69
+
70
+ @property
71
+ def attachment_key(self) -> str:
72
+ return self._data.get("attachmentKey", "") or ""
73
+
74
+ @property
75
+ def attributes(self) -> Dict[str, str]:
76
+ return self._attributes
77
+
78
+ @staticmethod
79
+ def _get_code_data_param(code: str, params: Optional[dict] = None):
80
+ """Retrieve raw code-table data using pre-built request parameters.
81
+
82
+ Args:
83
+ code (str): Name of the code library to read.
84
+ params (dict, optional): Query parameters to send to the API.
85
+
86
+ Returns:
87
+ list: Raw code entries returned by the API.
88
+ """
89
+ return get_connector().get_json(f"/api/Code/{code}", params=params or {})
90
+
91
+ @classmethod
92
+ def _get_code_data(
93
+ cls,
94
+ library: str,
95
+ scope: Optional[str] = None,
96
+ name: Optional[str] = None,
97
+ ):
98
+ """Retrieve raw code-table data with optional scope and name filters.
99
+
100
+ Args:
101
+ library (str): Name of the code library to read.
102
+ scope (str, optional): Scope (installation code) to filter by.
103
+ name (str, optional): Criterion for code name (use % for wildcards).
104
+
105
+ Returns:
106
+ list: Raw code entries returned by the API.
107
+ """
108
+ params = {}
109
+ if scope is not None:
110
+ params["scope"] = scope
111
+ if name is not None:
112
+ params["name"] = name
113
+ return cls._get_code_data_param(library, params=params)
114
+
115
+ @classmethod
116
+ def get_codes(
117
+ cls,
118
+ library: str,
119
+ scope: Optional[str] = None,
120
+ name: Optional[str] = None,
121
+ ) -> List["Code"]:
122
+ """Get all codes from a library as Code objects.
123
+
124
+ Args:
125
+ library (str): Name of the library to read.
126
+ scope (str, optional): Scope (installation code) to filter by.
127
+ name (str, optional): Criterion for code name (use % for wildcards).
128
+
129
+ Returns:
130
+ List[Code]: List of Code objects from the library.
131
+ """
132
+ data = cls._get_code_data(library, scope=scope, name=name)
133
+ if not isinstance(data, list):
134
+ raise ValueError(f"Invalid data from api for library {library}")
135
+
136
+ return [cls(x, library=library) for x in data]
137
+
138
+ @staticmethod
139
+ def get_names(library: str, scope: Optional[str] = None) -> list:
140
+ """Get code names of a library as a list of strings. For dropdowns etc.
141
+
142
+ Args:
143
+ library (str): Name of the library to read.
144
+ scope (str, optional): Scope (installation code) to filter by.
145
+
146
+ Returns:
147
+ list: List of code name strings.
148
+ """
149
+ params = {}
150
+ if scope is not None:
151
+ params["scope"] = scope
152
+ return get_connector().get_json(f"/api/Code/NameList/{library}", params=params)
153
+
154
+ @staticmethod
155
+ def get_name_and_desc(library: str, scope: Optional[str] = None) -> list:
156
+ """Get codes of a library as a list of name and description objects.
157
+
158
+ Args:
159
+ library (str): Name of the library to read.
160
+ scope (str, optional): Scope (installation code) to filter by.
161
+
162
+ Returns:
163
+ list: List of objects with name, description and isValid.
164
+ """
165
+ params = {}
166
+ if scope is not None:
167
+ params["scope"] = scope
168
+ return get_connector().get_json(
169
+ f"/api/Code/NameAndDesc/{library}", params=params
170
+ )
@@ -1,6 +1,6 @@
1
1
  from typing import List
2
2
 
3
- from commonlib_reader.utils import get_code
3
+ from commonlib_reader.code import Code
4
4
 
5
5
 
6
6
  class Discipline:
@@ -45,7 +45,7 @@ class Discipline:
45
45
  Returns:
46
46
  List["Discipline"]: List of discipline objects
47
47
  """
48
- dis_data = get_code("Discipline", scope=scope)
48
+ dis_data = Code._get_code_data("Discipline", scope=scope)
49
49
 
50
50
  if not isinstance(dis_data, list) or not all(
51
51
  isinstance(x, dict) for x in dis_data
@@ -1,6 +1,7 @@
1
1
  from typing import List, Union
2
2
 
3
- from commonlib_reader.utils import attributes_list_to_dict, get_code
3
+ from commonlib_reader.code import Code
4
+ from commonlib_reader.utils import attributes_list_to_dict
4
5
 
5
6
 
6
7
  class Facility:
@@ -38,11 +39,14 @@ class Facility:
38
39
  self._data = Facility._get_facility_data(code=code)
39
40
  self._attributes = attributes_list_to_dict(self._data["attributes"])
40
41
 
42
+ # Backward compatibility aliases
41
43
  if self.is_stid:
42
44
  self.STID = self.identity
43
45
  self.instCode = self.identity # Alias for identity
44
46
  try:
45
- self.SAP = int(self.sap) # Alias for SAPPlant as integer
47
+ self.SAP = int(
48
+ self.sap
49
+ ) # Backward compatibility alias for SAPPlant as integer
46
50
  except (TypeError, ValueError):
47
51
  pass
48
52
 
@@ -104,7 +108,7 @@ class Facility:
104
108
  def ioc_plant(self) -> str:
105
109
  return self._attributes.get("IOCPlant", "")
106
110
 
107
- def isSTID(self):
111
+ def _check_is_stid(self) -> bool:
108
112
  """
109
113
  Checks if the facility is marked for STID.
110
114
 
@@ -117,6 +121,15 @@ class Facility:
117
121
 
118
122
  return False
119
123
 
124
+ def isSTID(self) -> bool:
125
+ """
126
+ Backward compatibility method. Use is_stid property instead.
127
+
128
+ Returns:
129
+ bool: True if the facility is marked for STID, False otherwise.
130
+ """
131
+ return self._check_is_stid()
132
+
120
133
  def resolve_gov_facility_name(self) -> str:
121
134
  """
122
135
  Retrieves the governmental facility name.
@@ -267,7 +280,7 @@ class Facility:
267
280
  List[dict]: A list of dictionaries, each representing facility data.
268
281
  """
269
282
  if cls._cache is None:
270
- code = get_code("Facility")
283
+ code = Code._get_code_data("Facility")
271
284
  if isinstance(code, list):
272
285
  cls._cache = code
273
286
  else:
@@ -1,20 +1,22 @@
1
1
  from typing import List, Union
2
+ from commonlib_reader.code import Code
2
3
  from commonlib_reader.facility import Facility
3
- from commonlib_reader.utils import attributes_list_to_dict, get_code
4
+ from commonlib_reader.utils import attributes_list_to_dict
4
5
 
5
6
 
6
7
  class IMS:
7
- _ims_codes = []
8
-
9
- """Class for IMS objects.
10
-
11
- properties:
12
- Facility (str) - Name of facility
13
- IMSType (str) - Type of IMS
14
- Alias (str) - Facility name aliases
15
- isValid (bool) - True if IMS is in operation
8
+ """Class for IMS (Information Management System) objects.
9
+
10
+ Properties:
11
+ facility (str): Name of the facility associated with this IMS.
12
+ type (str): Type of the IMS system.
13
+ alias (str): Facility name alias used by the IMS.
14
+ is_valid (bool): True if the IMS is in operation, False otherwise.
15
+ seeq_datasource (str): The Seeq datasource identifier for this IMS.
16
16
  """
17
17
 
18
+ _ims_codes = None
19
+
18
20
  def __init__(self, data: Union[str, dict]):
19
21
  """Instance constructor for IMS object. End-users should use static method IMS.from_facility()
20
22
 
@@ -33,7 +35,6 @@ class IMS:
33
35
  self._data = data
34
36
  self._attributes = attributes_list_to_dict(self._data["attributes"])
35
37
 
36
- self.isValid = data["isValid"]
37
38
  for attr in self._data["attributes"]:
38
39
  self.__setattr__(attr["definitionName"], attr["displayValue"])
39
40
 
@@ -51,7 +52,12 @@ class IMS:
51
52
 
52
53
  @property
53
54
  def is_valid(self) -> bool:
54
- return getattr(self, "isValid", False)
55
+ return self._data.get("isValid", False)
56
+
57
+ @property
58
+ def isValid(self) -> bool:
59
+ """Backward compatibility alias for is_valid."""
60
+ return self.is_valid
55
61
 
56
62
  @property
57
63
  def seeq_datasource(self) -> str:
@@ -88,10 +94,10 @@ class IMS:
88
94
  """Get list of IMS instance of entries in code library ApplicationIMS. Caches locally in memory.
89
95
 
90
96
  Returns:
91
- List[IMS]: list of IMS instance from entries in code library ApplicationIMS.
97
+ List[IMS]: List of IMS instances from entries in code library ApplicationIMS.
92
98
  """
93
- if cls._ims_codes is None or len(cls._ims_codes) == 0:
94
- cls._ims_codes = get_code("ApplicationIMS")
99
+ if cls._ims_codes is None:
100
+ cls._ims_codes = Code._get_code_data("ApplicationIMS")
95
101
 
96
102
  return [IMS(x) for x in cls._ims_codes]
97
103
 
@@ -0,0 +1,174 @@
1
+ from typing import List, Optional
2
+
3
+ from commonlib_reader.connector import get_connector
4
+
5
+
6
+ class AttributeDefinition:
7
+ """Definition of an attribute belonging to a CommonLib library.
8
+
9
+ Attributes:
10
+ name (str): Name of the attribute.
11
+ description (str): Description of the attribute.
12
+ required (bool): Whether the attribute is required.
13
+ include_in_identity (bool): Whether the attribute is part of the code identity.
14
+ regex (str): Validation pattern for the attribute value, if any.
15
+ reference_library_name (str): Library referenced by this attribute, if any.
16
+ """
17
+
18
+ def __init__(self, data: dict):
19
+ if not isinstance(data, dict):
20
+ raise ValueError("Input data must be a dict from api")
21
+ self._data = data
22
+
23
+ def __eq__(self, other):
24
+ if isinstance(other, AttributeDefinition):
25
+ return self._data == other._data
26
+ return False
27
+
28
+ def __str__(self):
29
+ return self.name
30
+
31
+ @property
32
+ def name(self) -> str:
33
+ return self._data.get("name", "")
34
+
35
+ @property
36
+ def description(self) -> str:
37
+ return self._data.get("description", "") or ""
38
+
39
+ @property
40
+ def required(self) -> bool:
41
+ return self._data.get("required", False)
42
+
43
+ @property
44
+ def include_in_identity(self) -> bool:
45
+ return self._data.get("includeInIdentity", False)
46
+
47
+ @property
48
+ def regex(self) -> str:
49
+ return self._data.get("regex", "") or ""
50
+
51
+ @property
52
+ def reference_library_name(self) -> str:
53
+ return self._data.get("referenceLibraryName", "") or ""
54
+
55
+
56
+ class Library:
57
+ """A CommonLib library (a code-table definition and attribute schema).
58
+
59
+ A Library defines a code table and the attributes available on its Code
60
+ entries. It is not a container of Code instances; retrieve entries with
61
+ Code.get_codes() using this library's name.
62
+
63
+ Attributes:
64
+ name (str): Name of the library.
65
+ description (str): Description of the library.
66
+ is_valid (bool): Whether the library can still be used with new data.
67
+ scope_type (str): The scope type of the library.
68
+ is_global (bool): Whether the library is global.
69
+ is_scope_specific (bool): Whether the library is scope specific.
70
+ attribute_definitions (List[AttributeDefinition]): Attribute definitions of the library.
71
+ """
72
+
73
+ def __init__(self, data: dict):
74
+ if not isinstance(data, dict):
75
+ raise ValueError("Input data must be a dict from api")
76
+ self._data = data
77
+
78
+ def __eq__(self, other):
79
+ if isinstance(other, Library):
80
+ return self._data == other._data
81
+ return False
82
+
83
+ def __str__(self):
84
+ return f"{self.name} - {self.description}"
85
+
86
+ @property
87
+ def name(self) -> str:
88
+ return self._data.get("name", "")
89
+
90
+ @property
91
+ def description(self) -> str:
92
+ return self._data.get("description", "") or ""
93
+
94
+ @property
95
+ def is_valid(self) -> bool:
96
+ return self._data.get("isValid", False)
97
+
98
+ @property
99
+ def scope_type(self) -> str:
100
+ return self._data.get("scopeType", "") or ""
101
+
102
+ @property
103
+ def is_global(self) -> bool:
104
+ return self._data.get("isGlobal", False)
105
+
106
+ @property
107
+ def is_scope_specific(self) -> bool:
108
+ return self._data.get("isScopeSpecific", False)
109
+
110
+ @property
111
+ def attribute_definitions(self) -> List[AttributeDefinition]:
112
+ return [
113
+ AttributeDefinition(x)
114
+ for x in self._data.get("attributeDefinitions", []) or []
115
+ ]
116
+
117
+ @classmethod
118
+ def get(cls, name: str) -> "Library":
119
+ """Get a single library by name.
120
+
121
+ Args:
122
+ name (str): Name of the library to retrieve.
123
+
124
+ Returns:
125
+ Library: The requested library object.
126
+
127
+ Raises:
128
+ ValueError: If the library is not found.
129
+ """
130
+ for lib in cls.get_all(name=name):
131
+ if lib.name.upper() == name.upper():
132
+ return lib
133
+ raise ValueError(f"Library {name} not found")
134
+
135
+ @classmethod
136
+ def get_all(
137
+ cls, name: Optional[str] = None, scope: Optional[str] = None
138
+ ) -> List["Library"]:
139
+ """Get libraries as a list of Library objects.
140
+
141
+ Args:
142
+ name (str, optional): Criterion for library name (use % for wildcards).
143
+ scope (str, optional): Return libraries for this scope only.
144
+
145
+ Returns:
146
+ List[Library]: List of Library objects.
147
+ """
148
+ params = {}
149
+ if name is not None:
150
+ params["name"] = name
151
+ if scope is not None:
152
+ params["scope"] = scope
153
+
154
+ data = get_connector().get_json("/api/Library", params=params)
155
+ if not isinstance(data, list):
156
+ raise ValueError("Invalid library data from api")
157
+
158
+ return [cls(x) for x in data]
159
+
160
+ @staticmethod
161
+ def get_names() -> List[str]:
162
+ """Get library names as a list of strings. For dropdowns etc.
163
+
164
+ Returns:
165
+ List[str]: Sorted list of library name strings.
166
+ """
167
+ library_names = get_connector().get_json(url="/api/Library/NameList")
168
+ if isinstance(library_names, list) and all(
169
+ isinstance(name, str) for name in library_names
170
+ ):
171
+ library_names.sort()
172
+ return library_names
173
+
174
+ raise ValueError("Wrong data from API when getting library names.")
@@ -0,0 +1,74 @@
1
+ from typing import List, Optional
2
+
3
+ from commonlib_reader.connector import get_connector
4
+
5
+
6
+ class Project:
7
+ """A project.
8
+
9
+ Attributes:
10
+ name (str): Name of the project.
11
+ description (str): Description of the project.
12
+ abbreviation (str): Project abbreviation / alias.
13
+ is_valid (bool): Whether the project can still be used with new data.
14
+ """
15
+
16
+ def __init__(self, data: dict):
17
+ if not isinstance(data, dict):
18
+ raise ValueError("Input data must be a dict from api")
19
+ self._data = data
20
+
21
+ def __eq__(self, other):
22
+ if isinstance(other, Project):
23
+ return self._data == other._data
24
+ return False
25
+
26
+ def __str__(self):
27
+ return f"{self.name} - {self.description}"
28
+
29
+ @property
30
+ def name(self) -> str:
31
+ return self._data.get("name", "")
32
+
33
+ @property
34
+ def description(self) -> str:
35
+ return self._data.get("description", "") or ""
36
+
37
+ @property
38
+ def is_valid(self) -> bool:
39
+ return self._data.get("isValid", False)
40
+
41
+ @property
42
+ def abbreviation(self) -> str:
43
+ return self._data.get("aliasName", "") or ""
44
+
45
+ @classmethod
46
+ def get_all(
47
+ cls,
48
+ name: Optional[str] = None,
49
+ abbreviation: Optional[str] = None,
50
+ only_valid: bool = True,
51
+ ) -> List["Project"]:
52
+ """Get projects as a list of Project objects.
53
+
54
+ Args:
55
+ name (str, optional): Criterion for project name (use % for wildcards).
56
+ abbreviation (str, optional): Criterion for project abbreviation (use % for wildcards).
57
+ only_valid (bool): If True, only return projects usable with new data. Defaults to True.
58
+
59
+ Returns:
60
+ List[Project]: List of Project objects.
61
+ """
62
+ params = {}
63
+ if name is not None:
64
+ params["name"] = name
65
+ if abbreviation is not None:
66
+ params["abbreviation"] = abbreviation
67
+ if not only_valid:
68
+ params["isValid"] = "null"
69
+
70
+ data = get_connector().get_json("/api/Project", params=params)
71
+ if not isinstance(data, list):
72
+ raise ValueError("Invalid project data from api")
73
+
74
+ return [cls(x) for x in data]
@@ -1,8 +1,7 @@
1
1
  from typing import List, Optional, Union
2
+ from commonlib_reader.code import Code
2
3
  from commonlib_reader.utils import (
3
4
  attributes_list_to_dict,
4
- get_code,
5
- get_code_param,
6
5
  query_sql,
7
6
  )
8
7
  import re
@@ -19,11 +18,11 @@ class TagFormatElement:
19
18
  """A class representing the elements constructing a tag number.
20
19
 
21
20
  Attributes:
22
- description (str)
23
- name (str)
24
- IsValid (bool)
25
- IsRequired (bool)
26
- IsSelected (bool)
21
+ description (str): Description of the tag format element.
22
+ name (str): Name of the tag format element.
23
+ is_valid (bool): Whether the tag format element is valid.
24
+ is_required (bool): Whether the tag format element is required.
25
+ is_selected (bool): Whether the tag format element is selected.
27
26
 
28
27
  """
29
28
 
@@ -35,10 +34,9 @@ class TagFormatElement:
35
34
  data (dict): Data containing different properties of a tag format element.
36
35
  """
37
36
  self._data = data
38
-
39
- self.IsValid = bool(data["IsValid"])
40
- self.IsRequired = bool(data["Required"])
41
- self.IsSelected = bool(data["SelectFlag"])
37
+ self._is_valid = bool(data["IsValid"])
38
+ self._is_required = bool(data["Required"])
39
+ self._is_selected = bool(data["SelectFlag"])
42
40
 
43
41
  @property
44
42
  def name(self) -> str:
@@ -50,15 +48,30 @@ class TagFormatElement:
50
48
 
51
49
  @property
52
50
  def is_valid(self) -> bool:
53
- return self.IsValid
51
+ return self._is_valid
52
+
53
+ @property
54
+ def IsValid(self) -> bool:
55
+ """Backward compatibility property for is_valid."""
56
+ return self.is_valid
54
57
 
55
58
  @property
56
59
  def is_required(self) -> bool:
57
- return self.IsRequired
60
+ return self._is_required
61
+
62
+ @property
63
+ def IsRequired(self) -> bool:
64
+ """Backward compatibility property for is_required."""
65
+ return self.is_required
58
66
 
59
67
  @property
60
68
  def is_selected(self) -> bool:
61
- return self.IsSelected
69
+ return self._is_selected
70
+
71
+ @property
72
+ def IsSelected(self) -> bool:
73
+ """Backward compatibility property for is_selected."""
74
+ return self.is_selected
62
75
 
63
76
  def get_TagFormat(self) -> "TagFormat":
64
77
  tag_format_id = self._data["TagFormat_ID"]
@@ -254,9 +267,9 @@ class TagType:
254
267
  inst_code (str): The facility code the tag type is associated with.
255
268
  name (str): The name of the tag type.
256
269
  description (str): A description of the tag type.
257
- is_valid (bool): A flag indicating whether the tag type is valid.
258
270
  tag_category (str): The category under which this tag type falls.
259
- STID_tag_Count (int): The number of STID tags associated with this tag type.
271
+ is_valid (bool): A flag indicating whether the tag type is valid.
272
+ stid_tag_count (int): The number of STID tags associated with this tag type.
260
273
  """
261
274
 
262
275
  def __init__(self, data: dict, inst_code: str):
@@ -270,10 +283,11 @@ class TagType:
270
283
  self._data = data
271
284
  self._attributes = attributes_list_to_dict(self._data["attributes"])
272
285
  self._inst_code = inst_code
286
+ self._stid_tag_count = 0
273
287
 
274
288
  for attr in self._data["attributes"]:
275
289
  if attr["definitionName"] == "STIDTagCount":
276
- self.STID_tag_Count = int(attr["displayValue"])
290
+ self._stid_tag_count = int(attr["displayValue"])
277
291
 
278
292
  @property
279
293
  def inst_code(self) -> str:
@@ -295,9 +309,19 @@ class TagType:
295
309
  def is_valid(self) -> bool:
296
310
  return self._data["isValid"]
297
311
 
312
+ @property
313
+ def isValid(self) -> bool:
314
+ """Backward compatibility property for is_valid."""
315
+ return self.is_valid
316
+
298
317
  @property
299
318
  def stid_tag_count(self) -> int:
300
- return self.STID_tag_Count
319
+ return self._stid_tag_count
320
+
321
+ @property
322
+ def STID_tag_Count(self) -> int:
323
+ """Backward compatibility property for stid_tag_count."""
324
+ return self.stid_tag_count
301
325
 
302
326
  def __str__(self):
303
327
  return f"{self.name} - {self.description}"
@@ -336,13 +360,9 @@ class TagType:
336
360
  List[TagType]: A list of TagType objects matching the criteria.
337
361
  """
338
362
 
339
- params = {}
340
- params["scope"] = inst_code
341
- params["name"] = name
342
-
343
363
  tag_types = [
344
364
  TagType(x, inst_code=inst_code)
345
- for x in get_code_param("TagType", params=params)
365
+ for x in Code._get_code_data("TagType", scope=inst_code, name=name)
346
366
  ]
347
367
 
348
368
  if tag_category:
@@ -455,14 +475,14 @@ class TagCategory:
455
475
  A class representing a Facility specific category of tag types.
456
476
 
457
477
  Attributes:
458
- inst_code (str): Installation code for the facilitye the TagCategory is associated with.
478
+ inst_code (str): Installation code for the facility the TagCategory is associated with.
459
479
  name (str): Name of the tag category.
460
480
  description (str): Description of the tag category.
461
481
  is_valid (bool): Flag indicating whether the tag category is considered valid.
462
- TagCategoryId (int): Unique identifier of the tag category.
463
- TagCategoryDlg (str): Additional dialog information associated with the tag category.
464
- IndividFlag (str): A flag indicating individual characteristics for the category.
465
- SapFlCategory (str): Corresponding SAP functional location category.
482
+ tag_category_id (int): Unique identifier of the tag category.
483
+ tag_category_dlg (str): Additional dialog information associated with the tag category.
484
+ individ_flag (str): A flag indicating individual characteristics for the category.
485
+ sap_fl_category (str): Corresponding SAP functional location category.
466
486
  """
467
487
 
468
488
  def __init__(self, data, inst_code):
@@ -476,16 +496,20 @@ class TagCategory:
476
496
  self._data = data
477
497
  self._inst_code = inst_code
478
498
  self._attributes = attributes_list_to_dict(self._data["attributes"])
499
+ self._tag_category_id = 0
500
+ self._tag_category_dlg = ""
501
+ self._individ_flag = ""
502
+ self._sap_fl_category = ""
479
503
 
480
504
  for attr in self._data["attributes"]:
481
505
  if attr["definitionName"] == "TagCategoryId":
482
- self.TagCategoryId = int(attr["displayValue"])
506
+ self._tag_category_id = int(attr["displayValue"])
483
507
  elif attr["definitionName"] == "TagCategoryDlg":
484
- self.TagCategoryDlg = attr["displayValue"]
508
+ self._tag_category_dlg = attr["displayValue"]
485
509
  elif attr["definitionName"] == "IndividFlag":
486
- self.IndividFlag = attr["displayValue"]
510
+ self._individ_flag = attr["displayValue"]
487
511
  elif attr["definitionName"] == "SapFlCategory":
488
- self.SapFlCategory = attr["displayValue"]
512
+ self._sap_fl_category = attr["displayValue"]
489
513
 
490
514
  self._types = []
491
515
  self._formats = []
@@ -506,25 +530,49 @@ class TagCategory:
506
530
  def is_valid(self) -> bool:
507
531
  return self._data["isValid"]
508
532
 
533
+ @property
534
+ def isValid(self) -> bool:
535
+ """Backward compatibility property for is_valid."""
536
+ return self.is_valid
537
+
509
538
  @property
510
539
  def tag_category_id(self) -> int:
511
- id = self._attributes.get("TagCategoryId")
512
- if id is None:
540
+ id = self._tag_category_id
541
+ if id == 0:
513
542
  raise ValueError("TagCategoryId attribute not found")
514
-
515
543
  return int(id)
516
544
 
545
+ @property
546
+ def TagCategoryId(self) -> int:
547
+ """Backward compatibility property for tag_category_id."""
548
+ return self.tag_category_id
549
+
517
550
  @property
518
551
  def tag_category_dlg(self) -> str:
519
- return self._attributes.get("TagCategoryDlg", "")
552
+ return self._tag_category_dlg
553
+
554
+ @property
555
+ def TagCategoryDlg(self) -> str:
556
+ """Backward compatibility property for tag_category_dlg."""
557
+ return self.tag_category_dlg
520
558
 
521
559
  @property
522
560
  def individ_flag(self) -> str:
523
- return self._attributes.get("IndividFlag", "")
561
+ return self._individ_flag
562
+
563
+ @property
564
+ def IndividFlag(self) -> str:
565
+ """Backward compatibility property for individ_flag."""
566
+ return self.individ_flag
524
567
 
525
568
  @property
526
569
  def sap_fl_category(self) -> str:
527
- return self._attributes.get("SapFlCategory", "")
570
+ return self._sap_fl_category
571
+
572
+ @property
573
+ def SapFlCategory(self) -> str:
574
+ """Backward compatibility property for sap_fl_category."""
575
+ return self.sap_fl_category
528
576
 
529
577
  def get_formats(self) -> List[TagFormat]:
530
578
  """Get tag formats for this specific tag category.
@@ -625,7 +673,9 @@ def _get_category_data(inst_code: str) -> List[dict]:
625
673
  global _cache
626
674
 
627
675
  if inst_code not in _cache["TagCategory"].keys():
628
- _cache["TagCategory"][inst_code] = get_code("TagCategory", scope=inst_code)
676
+ _cache["TagCategory"][inst_code] = Code._get_code_data(
677
+ "TagCategory", scope=inst_code
678
+ )
629
679
 
630
680
  return _cache["TagCategory"][inst_code]
631
681
 
@@ -642,7 +692,7 @@ def _get_type_data(inst_code: str) -> List[dict]:
642
692
  global _cache
643
693
 
644
694
  if inst_code not in _cache["TagType"].keys():
645
- _cache["TagType"][inst_code] = get_code("TagType", scope=inst_code)
695
+ _cache["TagType"][inst_code] = Code._get_code_data("TagType", scope=inst_code)
646
696
 
647
697
  return _cache["TagType"][inst_code]
648
698
 
@@ -656,7 +706,7 @@ def _get_master_type_data(scope="") -> List[dict]:
656
706
  global _cache
657
707
 
658
708
  if "MasterTagType" not in _cache.keys():
659
- _cache["MasterTagType"] = get_code("MasterTagType")
709
+ _cache["MasterTagType"] = Code._get_code_data("MasterTagType")
660
710
 
661
711
  return _cache["MasterTagType"]
662
712
 
@@ -673,6 +723,8 @@ def _get_format_data(inst_code: str) -> List[dict]:
673
723
  global _cache
674
724
 
675
725
  if inst_code not in _cache["TagFormat"].keys():
676
- _cache["TagFormat"][inst_code] = get_code("TagFormat", scope=inst_code)
726
+ _cache["TagFormat"][inst_code] = Code._get_code_data(
727
+ "TagFormat", scope=inst_code
728
+ )
677
729
 
678
730
  return _cache["TagFormat"][inst_code]
@@ -1,10 +1,19 @@
1
- from typing import Union
1
+ from typing import List, Union
2
2
 
3
- from commonlib_reader.utils import get_code
3
+ from commonlib_reader.code import Code
4
4
 
5
5
 
6
6
  class Unit:
7
- _units = []
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
8
17
 
9
18
  def __init__(self, data: Union[dict, str]):
10
19
  if isinstance(data, str):
@@ -26,41 +35,48 @@ class Unit:
26
35
  raise ValueError("Input data must be a dict from codetable")
27
36
 
28
37
  @property
29
- def name(self):
38
+ def name(self) -> str:
39
+ """Get the unit name."""
30
40
  return getattr(self, "_name", "")
31
41
 
32
42
  @name.setter
33
- def name(self, value):
43
+ def name(self, value: str):
44
+ """Set the unit name."""
34
45
  self._name = value
35
46
 
36
47
  @property
37
- def description(self):
48
+ def description(self) -> str:
49
+ """Get the unit description."""
38
50
  return getattr(self, "_description", "")
39
51
 
40
52
  @description.setter
41
- def description(self, value):
53
+ def description(self, value: str):
54
+ """Set the unit description."""
42
55
  self._description = value
43
56
 
44
57
  @property
45
- def identity(self):
58
+ def identity(self) -> str:
59
+ """Get the unit identity."""
46
60
  return getattr(self, "_identity", "")
47
61
 
48
62
  @identity.setter
49
- def identity(self, value):
63
+ def identity(self, value: str):
64
+ """Set the unit identity."""
50
65
  self._identity = value
51
66
 
52
67
  @property
53
- def quantity(self):
68
+ def quantity(self) -> str:
69
+ """Get the quantity measured by this unit."""
54
70
  return getattr(self, "Quantity", "")
55
71
 
56
72
  @classmethod
57
- def get_units(cls):
73
+ def get_units(cls) -> List["Unit"]:
58
74
  """Get list of Unit objects of entries in code library. Caches locally in memory.
59
75
 
60
76
  Returns:
61
- List[IMS]: list of Unit objects from entries in code library.
77
+ List[Unit]: List of Unit objects from entries in code library.
62
78
  """
63
- if cls._units is None or len(cls._units) == 0:
64
- cls._units = get_code("UnitOfMeasure")
79
+ if cls._units is None:
80
+ cls._units = Code._get_code_data("UnitOfMeasure")
65
81
 
66
82
  return [Unit(x) for x in cls._units]
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import Dict, List, Optional
2
+ from typing import Dict, List
3
3
  from urllib.parse import urljoin
4
4
 
5
5
  import requests
@@ -7,43 +7,6 @@ import requests
7
7
  from commonlib_reader.connector import get_connector
8
8
 
9
9
 
10
- def get_library_names() -> list[str]:
11
- library_names = get_connector().get_json(url="/api/Library/NameList")
12
- if isinstance(library_names, list) and all(
13
- [isinstance(x, str) for x in library_names]
14
- ):
15
- library_names.sort() # type: ignore
16
- return library_names # type: ignore
17
-
18
- raise ValueError("Wrong data from API when getting library names.")
19
-
20
-
21
- def get_code(code: str, scope: Optional[str] = None, name: Optional[str] = None):
22
- params = {}
23
- if scope is not None:
24
- params["scope"] = scope
25
-
26
- if name is not None:
27
- params["name"] = name
28
- return get_code_param(code=code, params=params)
29
-
30
-
31
- def get_code_param(code, params=None):
32
- """_summary_
33
-
34
- Args:
35
- code (_type_): _description_
36
- params (_type_, optional): _description_. Defaults to None.
37
-
38
- Returns:
39
- _type_: _description_
40
- """
41
- if params is None:
42
- params = {}
43
-
44
- return get_connector().get_json(f"/api/Code/{code}", params=params)
45
-
46
-
47
10
  def query_sql(sql: str):
48
11
  return post_sql(sql=sql, take=0, skip=0)
49
12
 
@@ -1,39 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: commonlib-reader
3
- Version: 1.2.1
4
- Summary: Reader for Equinor commonlib api.
5
- Author: Åsmund Våge Fannemel
6
- Author-email: Åsmund Våge Fannemel <asmf@equinor.com>
7
- License: MIT
8
- Requires-Dist: pandas>=2.1.4
9
- Requires-Dist: eq-api-connector>=1.1.0,<2.0.0
10
- Requires-Python: >=3.10, <4.0.0
11
- Project-URL: Repository, https://github.com/equinor/commonlib-reader.git
12
- Description-Content-Type: text/markdown
13
-
14
- # commonlib-reader
15
- Connector package for Equinor [Commonlib](https://commonlib.equinor.com/) [api](https://commonlibapi.equinor.com/swagger/index.html).
16
-
17
- Current features:
18
- - Reading code tables
19
- - Getting facility data using the [Facility](commonlib_reader/facility.py) class
20
- - [IMS source ](commonlib_reader/ims.py) lookup tables for facilities
21
- - Getting Tag category, Tag type, Tag format, and Tag format element data. See [tag.py](commonlib_reader/tag.py)
22
- - Getting [units of measure](commonlib_reader/ims.py) definitions.
23
-
24
-
25
- ## Use
26
- Try it out by running the [demo](examples/demo.py).
27
-
28
- ## Installing
29
-
30
- Install package from pypi using `pip install commonlib_reader`
31
-
32
-
33
- ## Developing / testing
34
-
35
- uv is preferred for developers. Clone and install with required packages for testing and coverage:
36
- `uv sync`
37
-
38
- For testing with coverage run:
39
- `uv run pytest --cov --cov-report=html`
@@ -1,26 +0,0 @@
1
- # commonlib-reader
2
- Connector package for Equinor [Commonlib](https://commonlib.equinor.com/) [api](https://commonlibapi.equinor.com/swagger/index.html).
3
-
4
- Current features:
5
- - Reading code tables
6
- - Getting facility data using the [Facility](commonlib_reader/facility.py) class
7
- - [IMS source ](commonlib_reader/ims.py) lookup tables for facilities
8
- - Getting Tag category, Tag type, Tag format, and Tag format element data. See [tag.py](commonlib_reader/tag.py)
9
- - Getting [units of measure](commonlib_reader/ims.py) definitions.
10
-
11
-
12
- ## Use
13
- Try it out by running the [demo](examples/demo.py).
14
-
15
- ## Installing
16
-
17
- Install package from pypi using `pip install commonlib_reader`
18
-
19
-
20
- ## Developing / testing
21
-
22
- uv is preferred for developers. Clone and install with required packages for testing and coverage:
23
- `uv sync`
24
-
25
- For testing with coverage run:
26
- `uv run pytest --cov --cov-report=html`