tagreader 6.0.1__tar.gz → 6.0.3__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.
- {tagreader-6.0.1 → tagreader-6.0.3}/PKG-INFO +16 -23
- {tagreader-6.0.1 → tagreader-6.0.3}/README.md +5 -13
- {tagreader-6.0.1 → tagreader-6.0.3}/pyproject.toml +6 -7
- {tagreader-6.0.1 → tagreader-6.0.3}/tagreader/clients.py +33 -20
- {tagreader-6.0.1 → tagreader-6.0.3}/tagreader/web_handlers.py +46 -26
- {tagreader-6.0.1 → tagreader-6.0.3}/LICENSE +0 -0
- {tagreader-6.0.1 → tagreader-6.0.3}/tagreader/__init__.py +0 -0
- {tagreader-6.0.1 → tagreader-6.0.3}/tagreader/__version__.py +0 -0
- {tagreader-6.0.1 → tagreader-6.0.3}/tagreader/cache.py +0 -0
- {tagreader-6.0.1 → tagreader-6.0.3}/tagreader/logger.py +0 -0
- {tagreader-6.0.1 → tagreader-6.0.3}/tagreader/utils.py +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: tagreader
|
|
3
|
-
Version: 6.0.
|
|
3
|
+
Version: 6.0.3
|
|
4
4
|
Summary: Tagreader is a Python package for reading trend data from the OSIsoft PI and Aspen Infoplus.21 IMS systems.
|
|
5
|
-
Home-page: https://github.com/equinor/tagreader-python
|
|
6
5
|
License: MIT
|
|
7
6
|
Keywords: Aspen InfoPlus.21,OSIsoft PI
|
|
8
7
|
Author: Einar S. Idsø
|
|
9
8
|
Author-email: eiids@equinor.com
|
|
10
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.9.2,<4.0.0
|
|
11
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
11
|
Classifier: Intended Audience :: Developers
|
|
13
12
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -17,25 +16,27 @@ Classifier: Operating System :: MacOS
|
|
|
17
16
|
Classifier: Operating System :: Microsoft :: Windows
|
|
18
17
|
Classifier: Operating System :: POSIX :: Linux
|
|
19
18
|
Classifier: Programming Language :: Python :: 3
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
22
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
21
|
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
25
|
Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
|
|
26
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
27
|
Classifier: Topic :: Utilities
|
|
28
28
|
Provides-Extra: notebooks
|
|
29
|
-
Requires-Dist: certifi (>=2024.
|
|
29
|
+
Requires-Dist: certifi (>=2024.12.14,<2026.0.0)
|
|
30
30
|
Requires-Dist: diskcache (>=5.6.1,<6.0.0)
|
|
31
|
-
Requires-Dist: matplotlib (>=3.7.
|
|
32
|
-
Requires-Dist: msal-bearer (>=
|
|
33
|
-
Requires-Dist: notebook (>=7.2.
|
|
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"
|
|
34
34
|
Requires-Dist: pandas (>=1)
|
|
35
35
|
Requires-Dist: pycryptodome (>=3.20.0,<4.0.0)
|
|
36
36
|
Requires-Dist: requests (>=2,<3)
|
|
37
37
|
Requires-Dist: requests-kerberos (>=0,<1)
|
|
38
38
|
Requires-Dist: requests-ntlm (>=1.1,<=2.0)
|
|
39
|
+
Project-URL: Homepage, https://github.com/equinor/tagreader-python
|
|
39
40
|
Project-URL: Repository, https://github.com/equinor/tagreader-python
|
|
40
41
|
Description-Content-Type: text/markdown
|
|
41
42
|
|
|
@@ -47,27 +48,19 @@ Description-Content-Type: text/markdown
|
|
|
47
48
|
[](https://pepy.tech/project/tagreader)
|
|
48
49
|
|
|
49
50
|
Tagreader is a Python package for reading timeseries data from the OSIsoft PI and Aspen Infoplus.21
|
|
50
|
-
Information
|
|
51
|
-
as possible to the backend historians.
|
|
51
|
+
Information Management Systems (IMS). It is intended to be easy to use, and present as similar interfaces
|
|
52
|
+
as possible to the backend plant historians.
|
|
52
53
|
|
|
53
54
|
## Installation
|
|
54
55
|
You can install tagreader directly into your project from pypi by using pip
|
|
55
|
-
or another package manager.
|
|
56
|
+
or another package manager. Supports Python version 3.9.2 and above.
|
|
56
57
|
|
|
57
|
-
```shell
|
|
58
|
+
```shell
|
|
58
59
|
pip install tagreader
|
|
59
60
|
```
|
|
60
61
|
|
|
61
|
-
The following are required and will be installed:
|
|
62
|
-
|
|
63
|
-
* pandas
|
|
64
|
-
* requests
|
|
65
|
-
* requests-kerberos
|
|
66
|
-
* certifi
|
|
67
|
-
* diskcache
|
|
68
|
-
|
|
69
62
|
## Usage
|
|
70
|
-
Tagreader easy to use for both Equinor internal IMS services, and non-internal usage. For non-internal usage
|
|
63
|
+
Tagreader is easy to use for both Equinor internal IMS services, and non-internal usage. For non-internal usage
|
|
71
64
|
you simply need to provide the corresponding IMS service URLs and IMSType.
|
|
72
65
|
See [data source](https://equinor.github.io/tagreader-python/docs/about/usage/data-source) for details.
|
|
73
66
|
|
|
@@ -6,27 +6,19 @@
|
|
|
6
6
|
[](https://pepy.tech/project/tagreader)
|
|
7
7
|
|
|
8
8
|
Tagreader is a Python package for reading timeseries data from the OSIsoft PI and Aspen Infoplus.21
|
|
9
|
-
Information
|
|
10
|
-
as possible to the backend historians.
|
|
9
|
+
Information Management Systems (IMS). It is intended to be easy to use, and present as similar interfaces
|
|
10
|
+
as possible to the backend plant historians.
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
You can install tagreader directly into your project from pypi by using pip
|
|
14
|
-
or another package manager.
|
|
14
|
+
or another package manager. Supports Python version 3.9.2 and above.
|
|
15
15
|
|
|
16
|
-
```shell
|
|
16
|
+
```shell
|
|
17
17
|
pip install tagreader
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
The following are required and will be installed:
|
|
21
|
-
|
|
22
|
-
* pandas
|
|
23
|
-
* requests
|
|
24
|
-
* requests-kerberos
|
|
25
|
-
* certifi
|
|
26
|
-
* diskcache
|
|
27
|
-
|
|
28
20
|
## Usage
|
|
29
|
-
Tagreader easy to use for both Equinor internal IMS services, and non-internal usage. For non-internal usage
|
|
21
|
+
Tagreader is easy to use for both Equinor internal IMS services, and non-internal usage. For non-internal usage
|
|
30
22
|
you simply need to provide the corresponding IMS service URLs and IMSType.
|
|
31
23
|
See [data source](https://equinor.github.io/tagreader-python/docs/about/usage/data-source) for details.
|
|
32
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "tagreader"
|
|
3
|
-
version = "6.0.
|
|
3
|
+
version = "6.0.3"
|
|
4
4
|
description = "Tagreader is a Python package for reading trend data from the OSIsoft PI and Aspen Infoplus.21 IMS systems."
|
|
5
5
|
authors = ["Einar S. Idsø <eiids@equinor.com>", "Morten Dæhli Aslesen <masl@equinor.com"]
|
|
6
6
|
license = "MIT"
|
|
@@ -28,19 +28,18 @@ classifiers=[
|
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
[tool.poetry.dependencies]
|
|
31
|
-
python = "^3.
|
|
31
|
+
python = "^3.9.2"
|
|
32
32
|
pandas = ">=1"
|
|
33
|
-
certifi = "
|
|
33
|
+
certifi = ">=2024.12.14,<2026.0.0"
|
|
34
34
|
requests = "^2"
|
|
35
35
|
requests-kerberos = "^0"
|
|
36
|
-
msal-bearer = "
|
|
37
|
-
notebook = { version = "^7.2.
|
|
38
|
-
matplotlib = { version = "^3.7.
|
|
36
|
+
msal-bearer = "^1.3.0"
|
|
37
|
+
notebook = { version = "^7.2.2", optional = true }
|
|
38
|
+
matplotlib = { version = "^3.7.5", optional = true }
|
|
39
39
|
diskcache = "^5.6.1"
|
|
40
40
|
pycryptodome = "^3.20.0"
|
|
41
41
|
requests-ntlm = ">=1.1,<=2.0"
|
|
42
42
|
|
|
43
|
-
|
|
44
43
|
[tool.poetry.group.dev.dependencies]
|
|
45
44
|
pre-commit = "^3"
|
|
46
45
|
pytest = ">=7,<9"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from datetime import datetime, timedelta, tzinfo
|
|
2
|
+
from datetime import datetime, timedelta, timezone, tzinfo
|
|
3
3
|
from itertools import groupby
|
|
4
4
|
from operator import itemgetter
|
|
5
5
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
@@ -128,6 +128,8 @@ def get_server_address_aspen(datasource: str) -> Optional[Tuple[str, int]]:
|
|
|
128
128
|
host and port based on the path above and the UUID.
|
|
129
129
|
"""
|
|
130
130
|
|
|
131
|
+
# todo: is obsolete after removing ODBC
|
|
132
|
+
|
|
131
133
|
if not is_windows():
|
|
132
134
|
return None
|
|
133
135
|
import winreg
|
|
@@ -168,6 +170,7 @@ def get_server_address_pi(datasource: str) -> Optional[Tuple[str, int]]:
|
|
|
168
170
|
:return: host, port
|
|
169
171
|
:type: tuple(string, int)
|
|
170
172
|
"""
|
|
173
|
+
# todo: is obsolete after removing ODBC
|
|
171
174
|
|
|
172
175
|
if not is_windows():
|
|
173
176
|
return None
|
|
@@ -442,15 +445,20 @@ class IMSClient:
|
|
|
442
445
|
tags = [tags]
|
|
443
446
|
units = {}
|
|
444
447
|
for tag in tags:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
self.cache
|
|
453
|
-
|
|
448
|
+
try:
|
|
449
|
+
if self.cache is not None:
|
|
450
|
+
r = self.cache.get_metadata(key=tag, properties="unit")
|
|
451
|
+
if r is not None and "unit" in r:
|
|
452
|
+
units[tag] = r["unit"]
|
|
453
|
+
if tag not in units:
|
|
454
|
+
unit = self.handler._get_tag_unit(tag)
|
|
455
|
+
if self.cache is not None and unit is not None:
|
|
456
|
+
self.cache.put_metadata(key=tag, value={"unit": unit})
|
|
457
|
+
units[tag] = unit
|
|
458
|
+
except Exception:
|
|
459
|
+
if self.search(tag) == []: # check for nonexisting string
|
|
460
|
+
logger.warning(f"Tag not found: {tag}")
|
|
461
|
+
continue
|
|
454
462
|
return units
|
|
455
463
|
|
|
456
464
|
def get_descriptions(self, tags: Union[str, List[str]]) -> Dict[str, str]:
|
|
@@ -458,15 +466,20 @@ class IMSClient:
|
|
|
458
466
|
tags = [tags]
|
|
459
467
|
descriptions = {}
|
|
460
468
|
for tag in tags:
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
self.cache
|
|
469
|
-
|
|
469
|
+
try:
|
|
470
|
+
if self.cache is not None:
|
|
471
|
+
r = self.cache.get_metadata(key=tag, properties="description")
|
|
472
|
+
if r is not None and "description" in r:
|
|
473
|
+
descriptions[tag] = r["description"]
|
|
474
|
+
if tag not in descriptions:
|
|
475
|
+
desc = self.handler._get_tag_description(tag)
|
|
476
|
+
if self.cache is not None and desc is not None:
|
|
477
|
+
self.cache.put_metadata(key=tag, value={"description": desc})
|
|
478
|
+
descriptions[tag] = desc
|
|
479
|
+
except Exception:
|
|
480
|
+
if self.search(tag) == []: # check for nonexisting string
|
|
481
|
+
logger.warning(f"Tag not found: {tag}")
|
|
482
|
+
continue
|
|
470
483
|
return descriptions
|
|
471
484
|
|
|
472
485
|
def read_tags(
|
|
@@ -547,7 +560,7 @@ class IMSClient:
|
|
|
547
560
|
except ValueError:
|
|
548
561
|
start = convert_to_pydatetime(start)
|
|
549
562
|
if end is None:
|
|
550
|
-
end = datetime.
|
|
563
|
+
end = datetime.now(timezone.utc)
|
|
551
564
|
elif isinstance(end, (str, pd.Timestamp)):
|
|
552
565
|
end = convert_to_pydatetime(end)
|
|
553
566
|
|
|
@@ -72,7 +72,7 @@ def get_auth_aspen(use_internal: bool = True):
|
|
|
72
72
|
if use_internal:
|
|
73
73
|
return HTTPKerberosAuth(mutual_authentication=OPTIONAL)
|
|
74
74
|
|
|
75
|
-
from msal_bearer
|
|
75
|
+
from msal_bearer import BearerAuth
|
|
76
76
|
|
|
77
77
|
tenantID = "3aa4a235-b6e2-48d5-9195-7fcf05b459b0"
|
|
78
78
|
clientID = "7adaaa99-897f-428c-8a5f-4053db565b32"
|
|
@@ -187,6 +187,7 @@ class BaseHandlerWeb(ABC):
|
|
|
187
187
|
self.datasource = datasource
|
|
188
188
|
self.base_url = url
|
|
189
189
|
self.session = requests.Session()
|
|
190
|
+
self.auth = auth
|
|
190
191
|
self.session.auth = auth if auth is not None else get_auth_aspen()
|
|
191
192
|
if verify_ssl is False:
|
|
192
193
|
urllib3.disable_warnings(InsecureRequestWarning)
|
|
@@ -222,14 +223,6 @@ class BaseHandlerWeb(ABC):
|
|
|
222
223
|
txt = res.text.replace('"v":nan', '"v":NaN').replace('"v":-nan', '"v":NaN')
|
|
223
224
|
return json.loads(txt)
|
|
224
225
|
|
|
225
|
-
def fetch_text(
|
|
226
|
-
self, url, params: Optional[Union[str, Dict[str, str]]] = None
|
|
227
|
-
) -> str:
|
|
228
|
-
res = self.session.get(url, params=params)
|
|
229
|
-
res.raise_for_status()
|
|
230
|
-
|
|
231
|
-
return res.text
|
|
232
|
-
|
|
233
226
|
def connect(self):
|
|
234
227
|
try:
|
|
235
228
|
self.verify_connection(self.datasource)
|
|
@@ -267,6 +260,7 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
267
260
|
|
|
268
261
|
@staticmethod
|
|
269
262
|
def generate_connection_string(host, *_, **__):
|
|
263
|
+
# todo: is obsolete after removing ODBC
|
|
270
264
|
raise NotImplementedError
|
|
271
265
|
|
|
272
266
|
@staticmethod
|
|
@@ -503,7 +497,10 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
503
497
|
if not desc and not return_desc:
|
|
504
498
|
ret.append(tagname)
|
|
505
499
|
else:
|
|
506
|
-
|
|
500
|
+
try:
|
|
501
|
+
description = self._get_tag_description(tagname)
|
|
502
|
+
except KeyError:
|
|
503
|
+
description = ""
|
|
507
504
|
ret.append((tagname, description))
|
|
508
505
|
|
|
509
506
|
if not desc:
|
|
@@ -523,11 +520,11 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
523
520
|
query = self.generate_get_unit_query(tag)
|
|
524
521
|
url = urljoin(self.base_url, "TagInfo")
|
|
525
522
|
data = self.fetch(url, params=query)
|
|
526
|
-
try:
|
|
527
|
-
|
|
528
|
-
except KeyError as e:
|
|
529
|
-
|
|
530
|
-
|
|
523
|
+
# try:
|
|
524
|
+
attr_data = data["data"]["tags"][0]["attrData"]
|
|
525
|
+
# except KeyError as e:
|
|
526
|
+
# logger.error(f"Error. I got this: {data}")
|
|
527
|
+
# raise e
|
|
531
528
|
unit = ""
|
|
532
529
|
for a in attr_data:
|
|
533
530
|
if a["g"] == "Units":
|
|
@@ -559,8 +556,8 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
559
556
|
try:
|
|
560
557
|
data = self.fetch(url, params=query)
|
|
561
558
|
desc = data["data"]["tags"][0]["attrData"][0]["samples"][0]["v"]
|
|
562
|
-
except KeyError:
|
|
563
|
-
|
|
559
|
+
# except KeyError:
|
|
560
|
+
# desc = ""
|
|
564
561
|
except JSONDecodeError:
|
|
565
562
|
desc = ""
|
|
566
563
|
return desc
|
|
@@ -664,16 +661,23 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
664
661
|
'dso="CHARINT=N;CHARFLOAT=N;CHARTIME=N;CONVERTERRORS=N" '
|
|
665
662
|
f'm="{max_rows}" to="30" s="1">'
|
|
666
663
|
)
|
|
664
|
+
query = query.replace("\t", " ").replace(
|
|
665
|
+
"\n", " "
|
|
666
|
+
) # Replace new lines and tabs that are typical in formatted SQL queries with spaces.
|
|
667
|
+
|
|
668
|
+
# Need a solution to LIKE comments. These have a % symbol in the query that does not seem to pass through the request
|
|
667
669
|
|
|
668
670
|
connection_string += f"<![CDATA[{query}]]></SQL>"
|
|
669
671
|
return connection_string
|
|
670
672
|
|
|
671
673
|
def initialize_connection_string(
|
|
674
|
+
# todo: is obsolete after removing ODBC
|
|
672
675
|
self,
|
|
673
676
|
host: Optional[str] = None,
|
|
674
677
|
port: int = 10014,
|
|
675
678
|
connection_string: Optional[str] = None,
|
|
676
679
|
):
|
|
680
|
+
# todo: is obsolete after removing ODBC
|
|
677
681
|
if connection_string:
|
|
678
682
|
self._connection_string = connection_string
|
|
679
683
|
else:
|
|
@@ -703,13 +707,27 @@ class AspenHandlerWeb(BaseHandlerWeb):
|
|
|
703
707
|
max_rows=self._max_rows,
|
|
704
708
|
datasource=None,
|
|
705
709
|
)
|
|
706
|
-
|
|
707
|
-
|
|
710
|
+
|
|
711
|
+
res = self.session.get(url, params=params)
|
|
712
|
+
res.raise_for_status()
|
|
713
|
+
|
|
708
714
|
if parse:
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
715
|
+
parsed_dict = res.json()["data"][0]
|
|
716
|
+
|
|
717
|
+
cols = []
|
|
718
|
+
if "cols" in parsed_dict.keys():
|
|
719
|
+
for i in parsed_dict["cols"]:
|
|
720
|
+
cols.append(i["n"])
|
|
721
|
+
|
|
722
|
+
rows = []
|
|
723
|
+
if "rows" in parsed_dict.keys():
|
|
724
|
+
for i in parsed_dict["rows"]:
|
|
725
|
+
element = []
|
|
726
|
+
for j in i["fld"]:
|
|
727
|
+
element.append(j["v"])
|
|
728
|
+
rows.append(element)
|
|
729
|
+
return pd.DataFrame(data=rows, columns=cols)
|
|
730
|
+
return res.text
|
|
713
731
|
|
|
714
732
|
|
|
715
733
|
class PIHandlerWeb(BaseHandlerWeb):
|
|
@@ -742,6 +760,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
742
760
|
|
|
743
761
|
@staticmethod
|
|
744
762
|
def generate_connection_string(host, *_, **__):
|
|
763
|
+
# todo: is obsolete after removing ODBC
|
|
745
764
|
raise NotImplementedError
|
|
746
765
|
|
|
747
766
|
@staticmethod
|
|
@@ -777,6 +796,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
777
796
|
tag: Optional[str],
|
|
778
797
|
desc: Optional[str],
|
|
779
798
|
datasource: Optional[str],
|
|
799
|
+
auth: Optional[Any] = None,
|
|
780
800
|
) -> Dict[str, str]:
|
|
781
801
|
q = []
|
|
782
802
|
if tag is not None:
|
|
@@ -788,7 +808,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
788
808
|
|
|
789
809
|
if datasource is not None:
|
|
790
810
|
params["dataserverwebid"] = (
|
|
791
|
-
f"{get_piwebapi_source_to_webid_dict()[datasource]}"
|
|
811
|
+
f"{get_piwebapi_source_to_webid_dict(auth=auth)[datasource]}"
|
|
792
812
|
)
|
|
793
813
|
|
|
794
814
|
return params
|
|
@@ -900,7 +920,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
900
920
|
return_desc: bool = True,
|
|
901
921
|
) -> Union[List[Tuple[str, str]], List[str]]:
|
|
902
922
|
params = self.generate_search_params(
|
|
903
|
-
tag=tag, desc=desc, datasource=self.datasource
|
|
923
|
+
tag=tag, desc=desc, datasource=self.datasource, auth=self.auth
|
|
904
924
|
)
|
|
905
925
|
url = urljoin(self.base_url, "points", "search")
|
|
906
926
|
done = False
|
|
@@ -956,7 +976,7 @@ class PIHandlerWeb(BaseHandlerWeb):
|
|
|
956
976
|
return self.web_id_cache[tag]
|
|
957
977
|
|
|
958
978
|
params = self.generate_search_params(
|
|
959
|
-
tag=tag, datasource=self.datasource, desc=None
|
|
979
|
+
tag=tag, datasource=self.datasource, desc=None, auth=self.auth
|
|
960
980
|
)
|
|
961
981
|
url = urljoin(self.base_url, "points", "search")
|
|
962
982
|
data = self.fetch(url, params=params)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|