commonlib-reader 1.2.1__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.
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.3
2
+ Name: commonlib-reader
3
+ Version: 1.4.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
+ See the [changelog](CHANGELOG.md) for release history.
18
+
19
+ Current features:
20
+ - Reading any code table with the `Code` class
21
+ - Reading library definitions and attribute definitions with the `Library` and `AttributeDefinition` classes
22
+ - Reading projects with the `Project` class
23
+ - Getting facility data using the [Facility](commonlib_reader/facility.py) class
24
+ - [IMS source ](commonlib_reader/ims.py) lookup tables for facilities
25
+ - Getting Tag category, Tag type, Tag format, and Tag format element data. See [tag.py](commonlib_reader/tag.py)
26
+ - Getting [units of measure](commonlib_reader/ims.py) definitions.
27
+
28
+
29
+ ## Use
30
+ Try it out by running the [demo](examples/demo.py).
31
+
32
+ ### Libraries and codes
33
+
34
+ A `Library` defines a code table and its attribute schema; a `Code` is one entry
35
+ in that table. Use package-provided specialized readers when available, such as
36
+ `Facility`, `Discipline`, `Unit`, `TagType`, `TagCategory`, and `TagFormat`;
37
+ otherwise use the generic `Code` reader.
38
+
39
+ ### Libraries
40
+
41
+ Use `Library` to discover available code tables and inspect their definitions.
42
+
43
+ ```python
44
+ from commonlib_reader import Library
45
+
46
+ library_names = Library.get_names()
47
+ discipline_library = Library.get("Discipline")
48
+ attribute_definitions = discipline_library.attribute_definitions
49
+ is_scoped = discipline_library.is_scope_specific
50
+ scope_type = discipline_library.scope_type
51
+ ```
52
+
53
+ `Library.get_all()` supports name and scope filters. Each `AttributeDefinition`
54
+ provides its name, description, required status, identity participation, validation
55
+ regular expression, and referenced library name.
56
+
57
+ Check `is_scope_specific` and `scope_type` before retrieving codes. For example,
58
+ when `scope_type` is `"Facility"` and `is_scope_specific` is `True`, pass the
59
+ facility installation code, such as `"TROC"`, as `scope`.
60
+
61
+ ### Code tables
62
+
63
+ Use `Code.get_codes()` to retrieve entries from any CommonLib code table. Filter by
64
+ installation scope or code name when needed.
65
+
66
+ ```python
67
+ from commonlib_reader import Code
68
+
69
+ disciplines = Code.get_codes("Discipline", scope="TROC")
70
+ administration = Code.get_codes("Discipline", scope="TROC", name="A")
71
+ ```
72
+
73
+ Each entry provides `name`, `description`, `identity`, `is_valid`, `attributes`, and
74
+ other CommonLib metadata. Use `Code.get_names()` or `Code.get_name_and_desc()` when
75
+ only dropdown-friendly names or name/description data is required.
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
+
89
+ ## Installing
90
+
91
+ Install package from pypi using `pip install commonlib_reader`
92
+
93
+
94
+ ## Developing / testing
95
+
96
+ uv is preferred for developers. Clone and install with required packages for testing and coverage:
97
+ `uv sync`
98
+
99
+ For testing with coverage run:
100
+ `uv run pytest --cov --cov-report=html`
@@ -0,0 +1,87 @@
1
+ # commonlib-reader
2
+ Connector package for Equinor [Commonlib](https://commonlib.equinor.com/) [api](https://commonlibapi.equinor.com/swagger/index.html).
3
+
4
+ See the [changelog](CHANGELOG.md) for release history.
5
+
6
+ Current features:
7
+ - Reading any code table with the `Code` class
8
+ - Reading library definitions and attribute definitions with the `Library` and `AttributeDefinition` classes
9
+ - Reading projects with the `Project` class
10
+ - Getting facility data using the [Facility](commonlib_reader/facility.py) class
11
+ - [IMS source ](commonlib_reader/ims.py) lookup tables for facilities
12
+ - Getting Tag category, Tag type, Tag format, and Tag format element data. See [tag.py](commonlib_reader/tag.py)
13
+ - Getting [units of measure](commonlib_reader/ims.py) definitions.
14
+
15
+
16
+ ## Use
17
+ Try it out by running the [demo](examples/demo.py).
18
+
19
+ ### Libraries and codes
20
+
21
+ A `Library` defines a code table and its attribute schema; a `Code` is one entry
22
+ in that table. Use package-provided specialized readers when available, such as
23
+ `Facility`, `Discipline`, `Unit`, `TagType`, `TagCategory`, and `TagFormat`;
24
+ otherwise use the generic `Code` reader.
25
+
26
+ ### Libraries
27
+
28
+ Use `Library` to discover available code tables and inspect their definitions.
29
+
30
+ ```python
31
+ from commonlib_reader import Library
32
+
33
+ library_names = Library.get_names()
34
+ discipline_library = Library.get("Discipline")
35
+ attribute_definitions = discipline_library.attribute_definitions
36
+ is_scoped = discipline_library.is_scope_specific
37
+ scope_type = discipline_library.scope_type
38
+ ```
39
+
40
+ `Library.get_all()` supports name and scope filters. Each `AttributeDefinition`
41
+ provides its name, description, required status, identity participation, validation
42
+ regular expression, and referenced library name.
43
+
44
+ Check `is_scope_specific` and `scope_type` before retrieving codes. For example,
45
+ when `scope_type` is `"Facility"` and `is_scope_specific` is `True`, pass the
46
+ facility installation code, such as `"TROC"`, as `scope`.
47
+
48
+ ### Code tables
49
+
50
+ Use `Code.get_codes()` to retrieve entries from any CommonLib code table. Filter by
51
+ installation scope or code name when needed.
52
+
53
+ ```python
54
+ from commonlib_reader import Code
55
+
56
+ disciplines = Code.get_codes("Discipline", scope="TROC")
57
+ administration = Code.get_codes("Discipline", scope="TROC", name="A")
58
+ ```
59
+
60
+ Each entry provides `name`, `description`, `identity`, `is_valid`, `attributes`, and
61
+ other CommonLib metadata. Use `Code.get_names()` or `Code.get_name_and_desc()` when
62
+ only dropdown-friendly names or name/description data is required.
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
+
76
+ ## Installing
77
+
78
+ Install package from pypi using `pip install commonlib_reader`
79
+
80
+
81
+ ## Developing / testing
82
+
83
+ uv is preferred for developers. Clone and install with required packages for testing and coverage:
84
+ `uv sync`
85
+
86
+ For testing with coverage run:
87
+ `uv run pytest --cov --cov-report=html`
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "commonlib-reader"
3
+ version = "1.4.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.4.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,169 @@
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 directly for generic libraries, or as
12
+ a base class for dedicated readers that add library-specific behavior.
13
+
14
+ Attributes:
15
+ library (str): Name of the library the code belongs to.
16
+ name (str): The code name (the code value).
17
+ description (str): Description of the code.
18
+ is_valid (bool): Whether the code can still be used with new data.
19
+ identity (str): Unique identity used to cross reference the code.
20
+ iri (str): CommonLib IRI of the code.
21
+ project (str): Project the code belongs to, if any.
22
+ attachment_key (str): Key used to download an attached file, if any.
23
+ attributes (Dict[str, str]): Library specific attributes keyed by definition name.
24
+ """
25
+
26
+ def __init__(self, data: dict, library: str = ""):
27
+ if not isinstance(data, dict):
28
+ raise ValueError("Input data must be a dict from codetable")
29
+ self._data = data
30
+ self._library = library
31
+ self._attributes = attributes_list_to_dict(self._data.get("attributes", []))
32
+
33
+ def __eq__(self, other):
34
+ if isinstance(other, Code):
35
+ return self._data == other._data and self._library == other._library
36
+ return False
37
+
38
+ def __str__(self):
39
+ return f"{self.name} - {self.description}"
40
+
41
+ @property
42
+ def library(self) -> str:
43
+ return self._library
44
+
45
+ @property
46
+ def name(self) -> str:
47
+ return self._data.get("name", "")
48
+
49
+ @property
50
+ def description(self) -> str:
51
+ return self._data.get("description", "")
52
+
53
+ @property
54
+ def is_valid(self) -> bool:
55
+ return self._data.get("isValid", False)
56
+
57
+ @property
58
+ def identity(self) -> str:
59
+ return self._data.get("identity", "")
60
+
61
+ @property
62
+ def iri(self) -> str:
63
+ return self._data.get("iri", "") or ""
64
+
65
+ @property
66
+ def project(self) -> str:
67
+ return self._data.get("project", "") or ""
68
+
69
+ @property
70
+ def attachment_key(self) -> str:
71
+ return self._data.get("attachmentKey", "") or ""
72
+
73
+ @property
74
+ def attributes(self) -> Dict[str, str]:
75
+ return self._attributes
76
+
77
+ @staticmethod
78
+ def _get_code_data_param(code: str, params: Optional[dict] = None):
79
+ """Retrieve raw code-table data using pre-built request parameters.
80
+
81
+ Args:
82
+ code (str): Name of the code library to read.
83
+ params (dict, optional): Query parameters to send to the API.
84
+
85
+ Returns:
86
+ list: Raw code entries returned by the API.
87
+ """
88
+ return get_connector().get_json(f"/api/Code/{code}", params=params or {})
89
+
90
+ @classmethod
91
+ def _get_code_data(
92
+ cls,
93
+ library: str,
94
+ scope: Optional[str] = None,
95
+ name: Optional[str] = None,
96
+ ):
97
+ """Retrieve raw code-table data with optional scope and name filters.
98
+
99
+ Args:
100
+ library (str): Name of the code library to read.
101
+ scope (str, optional): Scope (installation code) to filter by.
102
+ name (str, optional): Criterion for code name (use % for wildcards).
103
+
104
+ Returns:
105
+ list: Raw code entries returned by the API.
106
+ """
107
+ params = {}
108
+ if scope is not None:
109
+ params["scope"] = scope
110
+ if name is not None:
111
+ params["name"] = name
112
+ return cls._get_code_data_param(library, params=params)
113
+
114
+ @classmethod
115
+ def get_codes(
116
+ cls,
117
+ library: str,
118
+ scope: Optional[str] = None,
119
+ name: Optional[str] = None,
120
+ ) -> List["Code"]:
121
+ """Get all codes from a library as Code objects.
122
+
123
+ Args:
124
+ library (str): Name of the library to read.
125
+ scope (str, optional): Scope (installation code) to filter by.
126
+ name (str, optional): Criterion for code name (use % for wildcards).
127
+
128
+ Returns:
129
+ List[Code]: List of Code objects from the library.
130
+ """
131
+ data = cls._get_code_data(library, scope=scope, name=name)
132
+ if not isinstance(data, list):
133
+ raise ValueError(f"Invalid data from api for library {library}")
134
+
135
+ return [cls(x, library=library) for x in data]
136
+
137
+ @staticmethod
138
+ def get_names(library: str, scope: Optional[str] = None) -> list:
139
+ """Get code names of a library as a list of strings. For dropdowns etc.
140
+
141
+ Args:
142
+ library (str): Name of the library to read.
143
+ scope (str, optional): Scope (installation code) to filter by.
144
+
145
+ Returns:
146
+ list: List of code name strings.
147
+ """
148
+ params = {}
149
+ if scope is not None:
150
+ params["scope"] = scope
151
+ return get_connector().get_json(f"/api/Code/NameList/{library}", params=params)
152
+
153
+ @staticmethod
154
+ def get_name_and_desc(library: str, scope: Optional[str] = None) -> list:
155
+ """Get codes of a library as a list of name and description objects.
156
+
157
+ Args:
158
+ library (str): Name of the library to read.
159
+ scope (str, optional): Scope (installation code) to filter by.
160
+
161
+ Returns:
162
+ list: List of objects with name, description and isValid.
163
+ """
164
+ params = {}
165
+ if scope is not None:
166
+ params["scope"] = scope
167
+ return get_connector().get_json(
168
+ f"/api/Code/NameAndDesc/{library}", params=params
169
+ )
@@ -1,40 +1,17 @@
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
- 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
@@ -45,7 +22,7 @@ class Discipline:
45
22
  Returns:
46
23
  List["Discipline"]: List of discipline objects
47
24
  """
48
- dis_data = get_code("Discipline", scope=scope)
25
+ dis_data = Code._get_code_data("Discipline", scope=scope)
49
26
 
50
27
  if not isinstance(dis_data, list) or not all(
51
28
  isinstance(x, dict) for x in dis_data
@@ -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
 
@@ -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