tagreader 6.0.4__tar.gz → 6.2.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,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: tagreader
3
- Version: 6.0.4
3
+ Version: 6.2.0
4
4
  Summary: Tagreader is a Python package for reading trend data from the OSIsoft PI and Aspen Infoplus.21 IMS systems.
5
- License: MIT
5
+ License-Expression: MIT
6
+ License-File: LICENSE
6
7
  Keywords: Aspen InfoPlus.21,OSIsoft PI
7
8
  Author: Einar S. Idsø
8
9
  Author-email: eiids@equinor.com
@@ -15,28 +16,23 @@ Classifier: Natural Language :: English
15
16
  Classifier: Operating System :: MacOS
16
17
  Classifier: Operating System :: Microsoft :: Windows
17
18
  Classifier: Operating System :: POSIX :: Linux
18
- Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
19
20
  Classifier: Programming Language :: Python :: 3.10
20
21
  Classifier: Programming Language :: Python :: 3.11
21
22
  Classifier: Programming Language :: Python :: 3.12
22
- Classifier: Programming Language :: Python :: 3.13
23
- Classifier: Programming Language :: Python :: 3.8
24
- Classifier: Programming Language :: Python :: 3.9
25
23
  Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
26
24
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
25
  Classifier: Topic :: Utilities
28
- Provides-Extra: notebooks
29
26
  Requires-Dist: certifi (>=2024.12.14,<2026.0.0)
30
- Requires-Dist: diskcache (>=5.6.1,<6.0.0)
31
- Requires-Dist: matplotlib (>=3.7.5,<4.0.0) ; extra == "notebooks"
32
- Requires-Dist: msal-bearer (>=1.3.0,<2.0.0)
33
- Requires-Dist: notebook (>=7.2.2,<8.0.0) ; extra == "notebooks"
27
+ Requires-Dist: diskcache (>=5.6.1)
28
+ Requires-Dist: msal-bearer (>=1.3.3)
34
29
  Requires-Dist: pandas (>=1)
35
- Requires-Dist: platformdirs (>=4.3.7,<5.0.0)
36
- Requires-Dist: pycryptodome (>=3.20.0,<4.0.0)
37
- Requires-Dist: requests (>=2,<3)
38
- Requires-Dist: requests-kerberos (>=0,<1)
30
+ Requires-Dist: platformdirs (>=4.3.7)
31
+ Requires-Dist: pycryptodome (>=3.20.0)
32
+ Requires-Dist: requests (>=2.32.5)
33
+ Requires-Dist: requests-kerberos (>=0)
39
34
  Requires-Dist: requests-ntlm (>=1.1,<=2.0)
35
+ Requires-Dist: urllib3 (>=2.5.0)
40
36
  Project-URL: Homepage, https://github.com/equinor/tagreader-python
41
37
  Project-URL: Repository, https://github.com/equinor/tagreader-python
42
38
  Description-Content-Type: text/markdown
@@ -1,14 +1,15 @@
1
- [tool.poetry]
1
+ [project]
2
2
  name = "tagreader"
3
- version = "6.0.4"
3
+ version = "6.2.0"
4
4
  description = "Tagreader is a Python package for reading trend data from the OSIsoft PI and Aspen Infoplus.21 IMS systems."
5
- authors = ["Einar S. Idsø <eiids@equinor.com>", "Morten Dæhli Aslesen <masl@equinor.com"]
5
+ authors = [
6
+ { name = "Einar S. Idsø", email = "eiids@equinor.com" },
7
+ { name = "Morten Dæhli Aslesen", email = "masl@equinor.com" }
8
+ ]
6
9
  license = "MIT"
7
10
  readme = "README.md"
8
- packages = [{include = "tagreader"}]
9
11
  keywords=["Aspen InfoPlus.21", "OSIsoft PI"]
10
- homepage = "https://github.com/equinor/tagreader-python"
11
- repository = "https://github.com/equinor/tagreader-python"
12
+ requires-python = ">=3.9.2,<4.0.0"
12
13
  classifiers=[
13
14
  "Development Status :: 5 - Production/Stable",
14
15
  "Intended Audience :: Developers",
@@ -18,36 +19,39 @@ classifiers=[
18
19
  "Operating System :: MacOS",
19
20
  "Operating System :: Microsoft :: Windows",
20
21
  "Operating System :: POSIX :: Linux",
21
- "Programming Language :: Python :: 3.8",
22
22
  "Programming Language :: Python :: 3.9",
23
23
  "Programming Language :: Python :: 3.10",
24
24
  "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
25
26
  "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator",
26
27
  "Topic :: Software Development :: Libraries :: Python Modules",
27
28
  "Topic :: Utilities",
28
29
  ]
30
+ dependencies = [
31
+ "pandas>=1",
32
+ "certifi>=2024.12.14,<2026.0.0",
33
+ "requests>=2.32.5",
34
+ "requests-kerberos>=0",
35
+ "msal-bearer>=1.3.3",
36
+ "diskcache>=5.6.1",
37
+ "pycryptodome>=3.20.0",
38
+ "requests-ntlm>=1.1,<=2.0",
39
+ "platformdirs>=4.3.7",
40
+ "urllib3>=2.5.0" # To avoid security issue https://www.cve.org/CVERecord?id=CVE-2025-50182
41
+ ]
29
42
 
30
- [tool.poetry.dependencies]
31
- python = "^3.9.2"
32
- pandas = ">=1"
33
- certifi = ">=2024.12.14,<2026.0.0"
34
- requests = "^2"
35
- requests-kerberos = "^0"
36
- msal-bearer = "^1.3.0"
37
- notebook = { version = "^7.2.2", optional = true }
38
- matplotlib = { version = "^3.7.5", optional = true }
39
- diskcache = "^5.6.1"
40
- pycryptodome = "^3.20.0"
41
- requests-ntlm = ">=1.1,<=2.0"
42
- platformdirs = "^4.3.7"
43
+ [tool.poetry.group.test.dependencies]
44
+ pytest = ">=7,<9"
45
+ # pytest-cov = ">=6.0.0"
43
46
 
44
47
  [tool.poetry.group.dev.dependencies]
45
- pre-commit = "^3"
46
- pytest = ">=7,<9"
48
+ # "black>=24.1.1",
49
+ pre-commit = ">=3.0"
47
50
 
48
- [tool.poetry.extras]
49
- notebooks = ["notebook", "matplotlib"]
51
+ [project.urls]
52
+ homepage = "https://github.com/equinor/tagreader-python"
53
+ repository = "https://github.com/equinor/tagreader-python"
50
54
 
51
55
  [build-system]
52
- requires = ["poetry-core"]
56
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
53
57
  build-backend = "poetry.core.masonry.api"
@@ -4,11 +4,18 @@ from tagreader.utils import (
4
4
  ReaderType,
5
5
  add_equinor_root_certificate,
6
6
  is_equinor,
7
- is_mac,
8
- is_windows,
9
7
  )
10
8
 
11
9
  if is_equinor():
12
10
  add_equinor_root_certificate()
13
11
 
14
12
  from tagreader.__version__ import version as __version__
13
+
14
+ __all__ = [
15
+ "IMSClient",
16
+ "list_sources",
17
+ "IMSType",
18
+ "ReaderType",
19
+ "add_equinor_root_certificate",
20
+ "__version__",
21
+ ]
@@ -32,7 +32,7 @@ def list_sources(
32
32
  imstype: Union[IMSType, str],
33
33
  url: Optional[str] = None,
34
34
  auth: Optional[Any] = None,
35
- verifySSL: bool = True,
35
+ verify_ssl: Optional[Union[bool, str]] = True,
36
36
  ) -> List[str]:
37
37
  if isinstance(imstype, str):
38
38
  try:
@@ -47,11 +47,11 @@ def list_sources(
47
47
  if imstype == IMSType.PIWEBAPI:
48
48
  if auth is None:
49
49
  auth = get_auth_pi()
50
- return list_piwebapi_sources(url=url, auth=auth, verify_ssl=verifySSL)
50
+ return list_piwebapi_sources(url=url, auth=auth, verify_ssl=verify_ssl)
51
51
  elif imstype == IMSType.ASPENONE:
52
52
  if auth is None:
53
53
  auth = get_auth_aspen()
54
- return list_aspenone_sources(url=url, auth=auth, verify_ssl=verifySSL)
54
+ return list_aspenone_sources(url=url, auth=auth, verify_ssl=verify_ssl)
55
55
  elif imstype in [IMSType.PI, IMSType.ASPEN, IMSType.IP21]:
56
56
  raise ValueError(
57
57
  f"ODBC clients are no longer supported. Given ims client type: {imstype}."
@@ -120,14 +120,14 @@ def get_handler(
120
120
  datasource: str,
121
121
  url: Optional[str],
122
122
  options: Dict[str, Union[int, float, str]],
123
- verifySSL: Optional[bool],
123
+ verify_ssl: Optional[Union[bool, str]],
124
124
  auth: Optional[Any],
125
125
  cache: Optional[Union[SmartCache, BucketCache]] = None,
126
126
  ):
127
127
  if imstype is None:
128
128
  try:
129
129
  if datasource in list_aspenone_sources(
130
- url=None, auth=None, verify_ssl=verifySSL
130
+ url=None, auth=None, verify_ssl=verify_ssl
131
131
  ):
132
132
  imstype = IMSType.ASPENONE
133
133
  except HTTPError as e:
@@ -135,7 +135,7 @@ def get_handler(
135
135
  if imstype is None:
136
136
  try:
137
137
  if datasource in list_piwebapi_sources(
138
- url=None, auth=None, verify_ssl=verifySSL
138
+ url=None, auth=None, verify_ssl=verify_ssl
139
139
  ):
140
140
  imstype = IMSType.PIWEBAPI
141
141
  except HTTPError as e:
@@ -146,7 +146,7 @@ def get_handler(
146
146
  url=url,
147
147
  datasource=datasource,
148
148
  options=options,
149
- verify_ssl=verifySSL,
149
+ verify_ssl=verify_ssl,
150
150
  auth=auth,
151
151
  cache=cache,
152
152
  )
@@ -156,7 +156,7 @@ def get_handler(
156
156
  datasource=datasource,
157
157
  url=url,
158
158
  options=options,
159
- verify_ssl=verifySSL,
159
+ verify_ssl=verify_ssl,
160
160
  auth=auth,
161
161
  )
162
162
  elif imstype in [IMSType.PI, IMSType.ASPEN, IMSType.IP21]:
@@ -178,7 +178,7 @@ class IMSClient:
178
178
  tz: Union[tzinfo, str] = pytz.timezone("Europe/Oslo"),
179
179
  url: Optional[str] = None,
180
180
  handler_options: Dict[str, Union[int, float, str]] = {}, # noqa:
181
- verifySSL: bool = True,
181
+ verify_ssl: Optional[Union[bool, str]] = True,
182
182
  auth: Optional[Any] = None,
183
183
  cache: Optional[Union[SmartCache, BucketCache]] = None,
184
184
  ):
@@ -209,7 +209,7 @@ class IMSClient:
209
209
  datasource=datasource,
210
210
  url=url,
211
211
  options=handler_options,
212
- verifySSL=verifySSL,
212
+ verify_ssl=verify_ssl,
213
213
  auth=auth,
214
214
  cache=self.cache,
215
215
  )
@@ -412,7 +412,7 @@ class IMSClient:
412
412
  ):
413
413
  start = start_time
414
414
  end = stop_time
415
- logger.warn(
415
+ logger.warning(
416
416
  (
417
417
  "This function has been renamed to read() and is deprecated. "
418
418
  "Please call 'read()' instead"
@@ -1,7 +1,7 @@
1
1
  import enum
2
2
  import hashlib
3
- import logging
4
3
  import platform
4
+ import socket
5
5
  import ssl
6
6
  from datetime import datetime, tzinfo
7
7
  from enum import Enum
@@ -33,7 +33,6 @@ if is_windows():
33
33
  import winreg
34
34
 
35
35
  if is_mac():
36
- import socket
37
36
  import subprocess
38
37
 
39
38
 
@@ -118,7 +117,7 @@ def add_equinor_root_certificate() -> bool:
118
117
  logger.debug(
119
118
  "Unable to locate Equinor Root CA certificate on this host. Downloading from Equinor server."
120
119
  )
121
- response = requests.get("http://pki.equinor.com/aia/ecpr.crt")
120
+ response = requests.get("http://pki.equinor.com/aia/ecpr.crt", timeout=20)
122
121
 
123
122
  if response.status_code != 200:
124
123
  logger.error(
@@ -145,6 +144,7 @@ def add_equinor_root_certificate() -> bool:
145
144
  with open(ca_file, "ab") as f:
146
145
  f.write(bytes(certificate, "ascii"))
147
146
  logger.debug("Equinor Root Certificate added to certifi store")
147
+ return True
148
148
 
149
149
 
150
150
  def find_local_equinor_root_certificate() -> str:
@@ -153,7 +153,7 @@ def find_local_equinor_root_certificate() -> str:
153
153
 
154
154
  if is_windows():
155
155
  logger.debug("Checking for Equinor Root CA in Windows certificate store")
156
- for cert in ssl.enum_certificates("CA"):
156
+ for cert in ssl.enum_certificates("CA"): # type: ignore
157
157
  found_cert = cert[0]
158
158
  # deepcode ignore InsecureHash: <Only hashes to compare with known hash>
159
159
  if hashlib.sha1(found_cert).hexdigest().upper() == equinor_root_pem_hash:
@@ -189,6 +189,9 @@ def get_macos_equinor_certificates():
189
189
  import ssl
190
190
  import tempfile
191
191
 
192
+ if not is_mac():
193
+ raise OSError("Function only works on MacOS")
194
+
192
195
  ca_search = "Equinor Root CA"
193
196
 
194
197
  ctx = ssl.create_default_context()
@@ -210,37 +213,84 @@ def is_equinor() -> bool:
210
213
  If Windows host:
211
214
  Finds host's domain in Windows Registry at
212
215
  HKLM\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Domain
216
+ or check if hostname starts with eqdev or eqpc
213
217
  If mac os host:
214
- Finds statoil.net as AD hostname in certificates
218
+ Finds statoil.net as AD hostname in certificates or
219
+ Finds statoil.net, client.statoil.net or equinor.com in dns search domains
215
220
  If Linux host:
216
- Checks whether statoil.no is search domain
221
+ Checks whether statoil.no is dns search domains
217
222
 
218
223
  Returns:
219
224
  bool: True if Equinor
220
225
  """
221
- if is_windows():
222
- with winreg.OpenKey(
223
- winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\ControlSet001\Services\Tcpip\Parameters"
224
- ) as key:
225
- domain = winreg.QueryValueEx(key, "Domain")
226
- if "statoil" in domain[0]:
227
- return True
228
- elif is_mac():
229
- s = subprocess.run(
230
- ["security", "find-certificate", "-a", "-c" "client.statoil.net"],
231
- stdout=subprocess.PIPE,
232
- ).stdout
233
226
 
234
- host = socket.gethostname()
227
+ hostname = socket.gethostname()
235
228
 
236
- # deepcode ignore IdenticalBranches: Not an error. First test is just more precise.
237
- if host + ".client.statoil.net" in str(s):
238
- return True
239
- elif "client.statoil.net" in host and host in str(s):
229
+ if is_windows():
230
+ if hostname.lower().startswith("eqdev") or hostname.lower().startswith("eqpc"):
240
231
  return True
241
- elif is_linux():
232
+ with winreg.OpenKey( # type: ignore
233
+ winreg.HKEY_LOCAL_MACHINE,
234
+ r"SYSTEM\ControlSet001\Services\Tcpip\Parameters", # type: ignore
235
+ ) as key:
236
+ domain = winreg.QueryValueEx(key, "Domain") # type: ignore
237
+ if "statoil" in domain[0] or "equinor" in domain[0]:
238
+ return True
239
+ elif is_linux() or is_mac():
242
240
  with open("/etc/resolv.conf", "r") as f:
243
- if "statoil.no" in f.read():
241
+ if any(
242
+ domain in f.read()
243
+ for domain in ["client.statoil.net", "statoil.net", "equinor.com"]
244
+ ):
245
+ return True
246
+
247
+ if is_mac():
248
+
249
+ def get_mac_dns_search_list():
250
+ """
251
+ Retrieves the DNS search list configured on macOS.
252
+ """
253
+ try:
254
+ # Execute the scutil command to get DNS configuration
255
+ result = subprocess.run(
256
+ ["scutil", "--dns"], capture_output=True, text=True, check=True
257
+ )
258
+ output = result.stdout
259
+
260
+ search_list = []
261
+ # Parse the output to find the search domains
262
+ for line in output.splitlines():
263
+ if "search domain" in line:
264
+ # Extract the domain from the line
265
+ parts = line.split(":")
266
+ if len(parts) > 1:
267
+ domain = parts[1].strip()
268
+ # Remove any leading/trailing quotes if present
269
+ domain = domain.strip('"')
270
+ search_list.append(domain)
271
+ return search_list
272
+ except subprocess.CalledProcessError as e:
273
+ print(f"Error executing scutil: {e}")
274
+ return []
275
+ except Exception as e:
276
+ print(f"An unexpected error occurred: {e}")
277
+ return []
278
+
279
+ if any(
280
+ domain in get_mac_dns_search_list()
281
+ for domain in ["client.statoil.net", "statoil.net", "equinor.com"]
282
+ ):
283
+ return True
284
+
285
+ s = subprocess.run(
286
+ ["security", "find-certificate", "-a", "-cclient.statoil.net"],
287
+ stdout=subprocess.PIPE,
288
+ ).stdout
289
+
290
+ # deepcode ignore IdenticalBranches: Not an error. First test is just more precise.
291
+ if hostname + ".client.statoil.net" in str(s):
292
+ return True
293
+ elif "client.statoil.net" in hostname and hostname in str(s):
244
294
  return True
245
295
  else:
246
296
  raise OSError(
@@ -42,12 +42,7 @@ def patched_hashlib_new(name, data=b"", usedforsecurity=True):
42
42
  if name.lower() == "md4":
43
43
  return MD4(data)
44
44
  else:
45
- # Try / Catch easier than detecting python version
46
- try:
47
- return hashlib_new_method(name, data=data, usedforsecurity=usedforsecurity)
48
- except TypeError:
49
- # Required for python 3.8
50
- return hashlib_new_method(name, data=data)
45
+ return hashlib_new_method(name, data=data, usedforsecurity=usedforsecurity)
51
46
 
52
47
 
53
48
  # Monkey-patch md4 in hashlib.new due to missing support for md4 in later releases of Python:
@@ -95,7 +90,7 @@ def get_url_aspen(use_internal: bool = True) -> str:
95
90
  def list_aspenone_sources(
96
91
  url: Optional[str] = None,
97
92
  auth: Optional[Any] = None,
98
- verify_ssl: Optional[bool] = True,
93
+ verify_ssl: Optional[Union[bool, str]] = True,
99
94
  ) -> List[str]:
100
95
  if url is None:
101
96
  url = get_url_aspen()
@@ -112,7 +107,7 @@ def list_aspenone_sources(
112
107
  url_ = urljoin(url, "DataSources")
113
108
  params = {"service": "ProcessData", "allQuotes": 1}
114
109
 
115
- res = requests.get(url_, params=params, auth=auth, verify=verify_ssl)
110
+ res = requests.get(url_, params=params, auth=auth, verify=verify_ssl, timeout=300)
116
111
  res.raise_for_status()
117
112
  try:
118
113
  source_list = [r["n"] for r in res.json()["data"] if r["t"] == "IP21"]
@@ -124,7 +119,7 @@ def list_aspenone_sources(
124
119
  def list_piwebapi_sources(
125
120
  url: Optional[str] = None,
126
121
  auth: Optional[Any] = None,
127
- verify_ssl: Optional[bool] = True,
122
+ verify_ssl: Optional[Union[bool, str]] = True,
128
123
  ) -> List[str]:
129
124
  if url is None:
130
125
  url = get_url_pi()
@@ -139,7 +134,7 @@ def list_piwebapi_sources(
139
134
  urllib3.disable_warnings(InsecureRequestWarning)
140
135
 
141
136
  url_ = urljoin(url, "dataservers")
142
- res = requests.get(url_, auth=auth, verify=verify_ssl)
137
+ res = requests.get(url_, auth=auth, verify=verify_ssl, timeout=300)
143
138
 
144
139
  res.raise_for_status()
145
140
  try:
@@ -152,7 +147,7 @@ def list_piwebapi_sources(
152
147
  def get_piwebapi_source_to_webid_dict(
153
148
  url: Optional[str] = None,
154
149
  auth: Optional[Any] = None,
155
- verify_ssl: Optional[bool] = True,
150
+ verify_ssl: Optional[Union[bool, str]] = True,
156
151
  ) -> List[str]:
157
152
  if url is None:
158
153
  url = get_url_pi()
@@ -167,7 +162,7 @@ def get_piwebapi_source_to_webid_dict(
167
162
  urllib3.disable_warnings(InsecureRequestWarning)
168
163
 
169
164
  url_ = urljoin(url, "dataservers")
170
- res = requests.get(url_, auth=auth, verify=verify_ssl)
165
+ res = requests.get(url_, auth=auth, verify=verify_ssl, timeout=300)
171
166
 
172
167
  res.raise_for_status()
173
168
  try:
@@ -182,7 +177,7 @@ class BaseHandlerWeb(ABC):
182
177
  datasource: Optional[str],
183
178
  url: Optional[str],
184
179
  auth: Optional[Any],
185
- verify_ssl: Optional[bool],
180
+ verify_ssl: Optional[Union[bool, str]],
186
181
  ):
187
182
  self.datasource = datasource
188
183
  self.base_url = url
@@ -213,6 +208,10 @@ class BaseHandlerWeb(ABC):
213
208
  logger.warning(f"No data found for {url} {params}")
214
209
  return {}
215
210
 
211
+ if res.text.startswith("XML Error"):
212
+ logger.warning(f"Invalid data returned for {url} {params}")
213
+ return {}
214
+
216
215
  try:
217
216
  return res.json()
218
217
  except JSONDecodeError:
@@ -241,7 +240,7 @@ class AspenHandlerWeb(BaseHandlerWeb):
241
240
  datasource: Optional[str],
242
241
  url: Optional[str] = None,
243
242
  auth: Optional[Any] = None,
244
- verify_ssl: Optional[bool] = True,
243
+ verify_ssl: Optional[Union[bool, str]] = True,
245
244
  options: Dict[str, Any] = dict(),
246
245
  ):
247
246
  if url is None:
@@ -317,12 +316,12 @@ class AspenHandlerWeb(BaseHandlerWeb):
317
316
  else:
318
317
  query = '<Q f="d" allQuotes="1">'
319
318
 
320
- query += "<Tag>" f"<N><![CDATA[{tagname}]]></N>"
319
+ query += f"<Tag><N><![CDATA[{tagname}]]></N>"
321
320
 
322
321
  if mapname:
323
322
  query += f"<M><![CDATA[{mapname}]]></M>"
324
323
 
325
- query += f"<D><![CDATA[{self.datasource}]]></D>" "<F><![CDATA[VAL]]></F>"
324
+ query += f"<D><![CDATA[{self.datasource}]]></D><F><![CDATA[VAL]]></F>"
326
325
 
327
326
  if read_type == ReaderType.SNAPSHOT:
328
327
  query += "<VS>1</VS>"
@@ -710,7 +709,7 @@ class PIHandlerWeb(BaseHandlerWeb):
710
709
  url: Optional[str],
711
710
  datasource: Optional[str],
712
711
  auth: Optional[Any],
713
- verify_ssl: bool,
712
+ verify_ssl: Optional[Union[bool, str]],
714
713
  options: Dict[str, Union[int, float, str]],
715
714
  cache: Optional[Union[SmartCache, BucketCache]],
716
715
  ):
@@ -844,10 +843,10 @@ class PIHandlerWeb(BaseHandlerWeb):
844
843
  if self._is_summary(read_type):
845
844
  params["selectedFields"] = "Links;Items.Value.Timestamp;Items.Value.Value"
846
845
  if get_status:
847
- params["selectedFields"] += (
848
- ";Items.Value.Good"
849
- ";Items.Value.Questionable"
850
- ";Items.Value.Substituted"
846
+ params[
847
+ "selectedFields"
848
+ ] += (
849
+ ";Items.Value.Good;Items.Value.Questionable;Items.Value.Substituted"
851
850
  )
852
851
  elif read_type in [ReaderType.INT, ReaderType.RAW, ReaderType.SHAPEPRESERVING]:
853
852
  params["selectedFields"] = "Links;Items.Timestamp;Items.Value"
File without changes
File without changes
File without changes
File without changes